Skip to content

fix(agent): keep MCP connection failures from cancelling gateway - #288

Closed
guix4ever wants to merge 208 commits into
EverMind-AI:mainfrom
guix4ever:fix/mcp_cancelled_error_escape
Closed

fix(agent): keep MCP connection failures from cancelling gateway#288
guix4ever wants to merge 208 commits into
EverMind-AI:mainfrom
guix4ever:fix/mcp_cancelled_error_escape

Conversation

@guix4ever

Copy link
Copy Markdown

Summary

  • Isolate a bare asyncio.CancelledError leaked by an MCP transport or session during connection setup.
  • Keep a failed MCP server from terminating gateway startup while logging the affected server.
  • Re-raise cancellation when Raven's own connection task has an active cancellation request.
  • Add deterministic regression coverage for both SDK-side and external cancellation paths.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

  • uv run pytest tests/test_sandbox_unit.py tests/test_tool_registry_timeout.py -x -q - 76 passed.

  • uv run --extra dev ruff check raven/agent/tools/mcp.py tests/test_sandbox_unit.py - passed.

  • uv run --extra dev ruff format --check raven/agent/tools/mcp.py tests/test_sandbox_unit.py - passed.

  • git diff --check - passed.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

No user-facing documentation or screenshots are required because this change only corrects connection failure isolation.

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

External gateway or task cancellation still propagates when Task.cancelling() reports an active cancellation request. Only a bare cancellation with no pending task cancellation is treated as an MCP server failure. Existing handling for ordinary exceptions and exception groups is unchanged. The change can be rolled back by reverting this commit.

Related Issues

Closes #231

0xKT and others added 30 commits June 29, 2026 00:23
Replace the hardcoded, stale commit-scope list in CLAUDE.md section 3.3
with a pointer to the README Repo layout section, and finish the
public-phase wording: drop the internal-AI-stats framing and the
GitLab-era JIRA / merge-request residue from the trailer rules, the
PR-open flow, and the PR template.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…e_and_template_cleanup

docs: point commit-scope at README and finish public-phase cleanup
…adius

Prompt injection can't be fully prevented, so this hardens defense-in-depth:
label untrusted content with a tamper-resistant boundary the model is told to
treat as data, and shrink the blast radius of a successful injection. Scope
follows the analysis report — high-value, low-complexity items; deeper items
(sandbox default flip, capability tokens, MCP/skill signing, memory audit log)
are deliberately deferred.

- security/trust.py: wrap_untrusted() fences external content with a per-call
  random nonce, so embedded fake close-markers can't escape the fence.
- system prompt: a guideline telling the model to treat fenced/external content
  as data, watch for "ignore the above" / "you are now" style directives, and
  confirm high-impact actions via ask_user.
- fence untrusted tool output at the single chokepoint
  (ContextBuilder.add_tool_result) and inside the subagent's own loop, plus the
  subagent result re-injected into the main agent.
- fence recalled memory and the sentinel's memory/attention context as
  unverified; the planner is told unverified content must not drive privileged
  actions (spawn / nudge).
- web_fetch: use security.network.validate_url_target (DNS-resolving SSRF check)
  instead of the previous scheme-only validator.
- DirectExecutor: pass only an env allowlist (no host secrets) rather than the
  full host environment; warn loudly when running unsandboxed (backend=none).
- subagents: per-session rolling-hour spawn rate limit (auto-recovering) to stop
  a prompt-injected re-injection loop without locking out legitimate use.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…-turns switch

Wire the TUI /model command end to end: model.* RPC handlers
(options / save_key / disconnect / add_model / remove_model); a
config.set "model" branch that rebuilds the live provider and reassigns
the agent loop so a switch takes effect on the next turn (guarded by
is_turn_active); ProviderConfig.models for the manual picker catalogue;
the custom provider routed through LiteLLM; and a base
LLMProvider.chat_stream non-streaming fallback so non-litellm providers
degrade gracefully instead of crashing. Frontend: the model picker gains
an api_base input, inline model add/delete, and a structured switch
through config.set; openrpc-derived types regenerated.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
feat: add TUI /model — provider/model config CRUD and between-turns model switch
…ning_untrusted_content

feat(security): label untrusted content and contain injection blast radius
Two related changes to the EverOS-backed memory subsystem.

Migrated three recall improvements from the legacy everclaw repo
(env vars renamed EVERCLAW_* -> RAVEN_*):
- drop current-session hits from the # Memory recall so it does not
  duplicate the live conversation already in the history window
  (over-fetch top_k*2, then filter by session_id)
- add `raven agent --message-file` for prompts beyond the shell ARG_MAX
- env-gated episodic recall + preference annotation in the consolidator
  (RAVEN_EPISODIC_TOPK / RAVEN_ANNOTATE_PREFS / RAVEN_EPISODIC_CLUSTER),
  default-off

Restructured when EverOS is invoked so native memory owns short-term and
EverOS owns long-term:
- recall is now on-miss: native runs every turn; EverOS user-track recall
  fires only when native surfaces nothing relevant to the query
- the per-turn EverOS store is removed; EverOS consolidation runs only
  (a) intra-day when a session crosses everos_consolidation_threshold_pct
  of the context window (default 80, background, non-blocking) and
  (b) nightly at everos_nightly_consolidation_hour (default 0, offline)
- backend.store gains force_flush to promote a batch immediately;
  consolidation is incremental per session (metadata watermark) so the
  two triggers never double-ingest
- native short-term compaction (maybe_consolidate_by_tokens) is unchanged

Tests: unit coverage for the threshold / nightly / on-miss paths plus a
full in-memory lifecycle smoke, and a real-everos e2e for the
force_flush single-store recall path.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
EverOS defaulted both its user-level config toml and its data root
(sqlite / lancedb / .index / ome.db) to a bare ~/.everos. Scope raven's
instance under ~/.everos/raven so config and data stay isolated from any
other EverOS consumer on the machine.

Add update_everos.configure_everos_env(): it sets EVEROS_CONFIG_FILE and
EVEROS_MEMORY__ROOT via setdefault (so an explicit operator override
still wins) before EverOS's @cache-d load_settings() first runs. The
everos backend factory and the understand_media tool factory call it as
the earliest raven-controlled EverOS entry points; _EVEROS_CONFIG now
derives from a single _EVEROS_BASE constant.

No data migration: existing ~/.everos data is left untouched and a fresh
~/.everos/raven is created lazily on first write/use.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
A fresh onboard now materializes the user-facing subset of the memory /
plugins / skillForge extension blocks into ~/.raven/config.json so the
knobs are discoverable and editable without reading the source. Seeding
is setdefault-based (non-clobbering) and runs on every onboard, so it
also backfills a pre-existing config that predates these blocks.

- memory.backend defaults to "everos" (schema default); _memory_enabled
  now gates on an llm model being present, so a fresh modelless seed is
  treated as "not yet enabled" and the skip / non-interactive guard still
  resolves it to None when memory is opted out or left unconfigured.
- skillForge.everos.enabled is seeded on; router.hub.endpoint is seeded
  to the shared Skill Hub URL with apiKey left null for the user to fill.
- plugins.config["everos-memory"] is seeded with identity wiring that
  mirrors memory.userId / agentId so recall identities match.
- internal-infra fields (embedding / reranker endpoints + their API keys,
  mass_library_db) are NOT written, so a real Bearer token never lands in
  a plaintext config; they stay at their schema defaults.

Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…lt_extension_blocks

Feat/onboard default extension blocks
…recall_improvements

feat: native short-term memory + everos long-term consolidation
…_memory_recall_improvements"

This reverts commit b88ea17, reversing
changes made to 5d9887e.
…_memory_recall

revert: pr EverMind-AI#9 native short-term memory + everos long-term consolidation
…readme_ci

chore: prepare open source launch
## Summary
- switch project metadata and license text to Apache-2.0 while retaining
upstream notices
- add public-ready community files: issue templates, SECURITY,
CONTRIBUTING, CODE_OF_CONDUCT, Dependabot, and a stricter PR template
- align README license copy with the public launch posture

## Repository settings updated via GitHub API
- squash-only merge enabled; merge commit and rebase merge disabled
- delete branch on merge and update branch enabled
- Discussions enabled; Projects and Wiki disabled
- Actions default workflow permissions set to read-only and PR
self-approval disabled
- description and homepage updated for Raven

## Verification
- npm ci --ignore-scripts --no-audit --no-fund (root)
- npm ci --ignore-scripts --no-audit --no-fund (ui-tui)
- npm ci --ignore-scripts --no-audit --no-fund (bridge)
- npx --no-install commitlint --from origin/main --to HEAD --config
commitlint.config.cjs
- PYTHONPATH=. python3 scripts/check_commit_messages.py
origin/main..HEAD
- uv run pre-commit run --from-ref origin/main --to-ref HEAD
- git diff --check origin/main...HEAD

## Notes
- GitHub branch protection/rulesets returned 403 while the repository is
private on the current plan. Enable required checks after making the
repository public.
- Auto-merge remained disabled after the API patch, likely due
repository/org availability or policy.
## Summary
- add lightweight overview files for top-level repository sections that
still displayed old non-conventional commit messages
- update existing benchmark and demo READMEs with public-facing guidance
- make the GitHub directory listing show a clean conventional commit
message after merge without rewriting main history

## Verification
- uv run pre-commit run --files LICENSES/README.md benchmarks/README.md
demos/README.md docs/README.md raven/README.md tests/README.md
- npx --no-install commitlint --from origin/main --to HEAD --config
commitlint.config.cjs
- PYTHONPATH=. python3 scripts/check_commit_messages.py
origin/main..HEAD
- uv run pre-commit run --from-ref origin/main --to-ref HEAD

## Note
A normal pull request cannot rename historical commits already merged
into main. This PR instead updates the affected directories with useful
public-facing overview docs so GitHub displays the new conventional
commit as the latest directory change.
Bumps
[python-socketio](https://github.com/miguelgrinberg/python-socketio)
from 5.16.1 to 5.16.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/releases">python-socketio's
releases</a>.</em></p>
<blockquote>
<h2>Release 5.16.2</h2>
<p>See <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">CHANGES.md</a>
for release notes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">python-socketio's
changelog</a>.</em></p>
<blockquote>
<h1>python-socketio change log</h1>
<p><strong>Release 5.16.3</strong> - 2026-06-15</p>
<ul>
<li>Catch all exceptions in redis and rabbitmq client managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1581">#1581</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/80bb5c9b07e6b53e45f610ec29a24124c539d41b">commit</a>)</li>
</ul>
<p><strong>Release 5.16.2</strong> - 2026-05-21</p>
<ul>
<li>Prevent unnecessary resource allocations <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b">commit</a>)</li>
<li>Add zizmor to CI builds <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642">commit</a>)</li>
</ul>
<p><strong>Release 5.16.1</strong> - 2026-02-06</p>
<ul>
<li>Use configured JSON module in managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1549">#1549</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6229261ae6e8c01e675097242e333ee84587a544">commit</a>)</li>
<li>Admin UI fixes: remove duplicate tasks, report transport upgrades
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1c2eab13a92fac9e43663eb0b5f099eb1c40ea5b">commit</a>)</li>
<li>Switch to Furo documentation template (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/add47d8c7abca697a2804141bbf29bfb095f7d5e">commit</a>)</li>
<li>Add Python free-threading to CI <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1554">#1554</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ccdd2004a038ae4b8171a05120c5d0787332f7ee">commit</a>)</li>
</ul>
<p><strong>Release 5.16.0</strong> - 2025-12-24</p>
<ul>
<li>Address deprecation warnings (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b235699d9b06564753c570b76055997e9d62a938">commit</a>)</li>
<li>Drop Python 3.8 and 3.9 from CI builds (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/d0728d2f74538762dd551fa9cd0cd1fd5aedfa37">commit</a>)</li>
</ul>
<p><strong>Release 5.15.1</strong> - 2025-12-16</p>
<ul>
<li>Restore support multiple arguments via pubsub emits <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1540">#1540</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c279f26bb8c9887c4ca99d4d81ad331c4844438c">commit</a>)</li>
</ul>
<p><strong>Release 5.15.0</strong> - 2025-11-22</p>
<ul>
<li>Retry initial Redis connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1536">#1534</a>
([commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1">#1</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad">https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad</a>)
[commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/2">#2</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d">https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d</a>))</li>
<li>Correctly regenerate RabbitMQ binding after a connection failure <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1516">#1516</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c52e93b4a328d98a968bfbdec0cfd598b73ee913">commit</a>)
(thanks <strong>Gritty_dev</strong>!)</li>
<li>Support <code>ext_type</code> in the <code>MsgPackPacket</code>
class <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1521">#1521</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/208925344a48485d2cd56e40eb74266c3bcb5311">commit</a>)</li>
<li>Support sending <code>bytesarray</code>s when using pub/sub managers
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6c9b9974f72e2efdf62407ecab24ee6995448098">commit</a>)</li>
<li>Fix typos in documentation <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1520">#1520</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/db3f1c2a0105c30cb833ddfca8f05fe4320468fd">commit</a>)
(thanks <strong>Lê Nam Khánh</strong>!)</li>
<li>Improvements to the logging documentation (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b423d0e38eef559b7e81acb7e32059de305f982c">commit</a>)</li>
</ul>
<p><strong>Release 5.14.3</strong> - 2025-10-29</p>
<ul>
<li>Support Python's native <code>ConnectionRefusedError</code>
exception to reject a connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1515">#1515</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3">commit</a>)</li>
<li>Push binary data to the aiopika client manager <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1514">#1514</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/194e1b7f277b5f72e1de78d3f614e7b8b6c788ac">commit</a>)</li>
</ul>
<p><strong>Release 5.14.2</strong> - 2025-10-15</p>
<ul>
<li>Restore binary message support in message queue setups <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1509">#1509</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3">commit</a>)</li>
<li>Fix formatting of client connection error <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1507">#1507</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f298c9b54d76ab09ff72935937e1b9575bc45ffd">commit</a>)</li>
<li>Add 3.14 and pypy-3.11 CI tasks (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1f4cd3b025c294f25208ec3c05b5f8df6209e403">commit</a>)</li>
<li>Improve documentation of the
<code>BaseManager.get_participants()</code> method (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/33722a0d96036f005188b07b8b46a5ef091fe65f">commit</a>)</li>
</ul>
<p><strong>Release 5.14.1</strong> - 2025-10-02</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/6e2b7175c97c2785edd2ea60df026c12240c785c"><code>6e2b717</code></a>
Release 5.16.2</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/cb658291085842372a16863d83e14373f85b3d3e"><code>cb65829</code></a>
update python-engineio version</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b"><code>ca140fe</code></a>
prevent unnecessary resource allocation (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a>)</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/b29beef6bfd38d5a3c3b89da96b7a4574a91efcf"><code>b29beef</code></a>
tox configuration</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/e8981302a3d5b525ee73c2ec9c13859ce2f764e8"><code>e898130</code></a>
Bump ujson from 5.4.0 to 5.12.1 in /examples/server/sanic (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1573">#1573</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/05c32f5da85fe5e5d647c937c7eab0972760ca05"><code>05c32f5</code></a>
Bump qs and body-parser in /examples/server/javascript (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1572">#1572</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/287dc6715b801d319f5baa0fb727e232e728b8aa"><code>287dc67</code></a>
Bump qs and body-parser in /examples/client/javascript (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1571">#1571</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642"><code>664dc27</code></a>
add zizmor to ci (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a>)</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/14c62366a85cfe34e0bd69a22b905eb329873dd3"><code>14c6236</code></a>
Bump django in /examples/server/wsgi/django_socketio (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1566">#1566</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/29b2e5cdbe87e8216bc46fccb864b84ab9f55cf9"><code>29b2e5c</code></a>
Bump aiohttp from 3.13.3 to 3.13.4 in /examples/server/aiohttp (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1565">#1565</a>)
#nolog</li>
<li>Additional commits viewable in <a
href="https://github.com/miguelgrinberg/python-socketio/compare/v5.16.1...v5.16.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python-socketio&package-manager=uv&previous-version=5.16.1&new-version=5.16.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/EverMind-AI/Raven/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
gloryfromca and others added 14 commits August 20, 2026 14:36
## Summary

Upgrades the pinned EverOS from `1.2.1` to `1.2.3`, per the SOP in
`docs/memory-plugin-architecture.md` section 7.2. Four packages move:
`everos`, `everalgo-agent-memory` `0.3.1` to `0.4.0`,
`everalgo-user-memory` `0.3.2` to `0.4.0`, and `lancedb` `0.33.0` to
`0.34.0`.

**The adapter needs no change.** Every EverOS symbol it imports still
exists
with an identical signature (12 call sites checked), and
`memory/search/dto.py`
plus the `search` / `memorize` / `get` route modules are byte-identical
between
the two releases, so the HTTP contract is untouched.

**No data migration.** No LanceDB table schema changed: `user_profile`
and
`knowledge_topic` are byte-identical between the two releases, and the
five
files that differ at all (`episode`, `atomic_fact`, `agent_case`,
`agent_skill`, `foresight`) differ only in docstrings. So the
column-type
startup check that `1.2.2` added does not fire on an index this upgrade
produces. `lancedb` `0.33` and `0.34` were verified interchangeable in
both
directions - each writes Lance file format `v2.1`, and a table written
by
either opens, searches over the other's IVF and FTS indexes, upserts and
prunes under the other - so reverting the pin can still read the index.

Three things reviewers should know, all recorded in the new SOP section
7.3
rather than only here:

1. **`extract_foresight` now ships disabled.** The default moved in
code, not
in `default_ome.toml`, so an existing `~/.everos/ome.toml` does not opt
out
   of the change. Any deployment relying on foresight entries must set
   `enabled = true` per install.
2. **Agent-skill extraction works for the first time.** Before `1.2.3` a
   cascade race meant it produced zero `SKILL.md` files, so
`EverosSkillSource` has been contributing nothing; it now contributes
real
   skills to the prompt.
3. **The Linux install floor moves to glibc 2.28.** `lancedb 0.34.0`
ships no
`manylinux_2_17` wheel and no sdist, and `install.sh` hands the exported
   lockfile to the user's `uv` as constraints, so CentOS or RHEL 7,
Ubuntu 18.04 and Amazon Linux 2 fail at install time rather than falling
   back to a source build.

Two smaller changes are consequences, not choices. `1.2.3` gives
`extract_agent_skill` a module-level `_reader` singleton beside
`_writer`, and
the integration conftest reset only the writers it knew about. And the
bundled
plugin manifest version moves to `1.2.0`, which two tests assert as a
literal;
SOP step 5 now names both so the next bump does not rediscover them.

## Type

- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other

Dependency upgrade. `chore` has no box of its own.

## Verification

```
uv run pytest tests/test_everos_plugin_discovery.py tests/test_everos_backend.py \
  tests/test_everos_http_adapter.py tests/test_memory_backend_protocol.py \
  tests/test_memory_backend_contract.py -q
=> 158 passed

uv run pytest <all unit files that mention everos> \
  tests/test_memory_backend_protocol.py tests/test_memory_backend_contract.py \
  -q -p no:randomly
=> 1336 passed, 30 skipped

uv run ruff check <the four changed .py files>          => All checks passed
uv run ruff format --check <the four changed .py files> => 4 files already formatted
make check-large-files                                  => pass
```

`make lint-python` on this branch only covers the nine CI-tooling files
in
`PYTHON_LINT_TARGETS`, so ruff was run directly against the files this
change
touches instead of relying on that target.

The 30 skips are parametrized cases in
`test_provider_resolution_invariants.py` ("not a gateway" and similar);
they
are structural, unrelated to EverOS, and skip identically on the
baseline.

Beyond the suites above, the upgrade was exercised end to end through
Raven's
own entrypoints on an isolated `RAVEN_HOME`: `raven plugins` reports the
new
manifest version, `raven doctor` still parses the capability matrix from
a
`/health` that `1.2.3` extended with a `cascade` block, and a real
`raven agent -m` turn recalls memories that `1.2.1` wrote - including
after
rolling the pin back, with the index having been written by `lancedb
0.34` in
between. The `extract_foresight` change is visible in the OME
`run_record`
table: every other strategy advances by one dispatch per turn while
`extract_foresight` stops being dispatched at all.

**The `real_llm` layer of SOP step 3 could not be validated, and this PR
does
not claim it passes.** All three tests in
`tests/integration/test_everos_backend_e2e.py` fail with
`no such table: md_change_state`, and they fail identically on the
baseline,
checked by re-syncing to the old pin and running the same command. The
cause is
pre-existing and independent of the upgrade: `everos_env` isolates the
test by
monkeypatching `EVEROS_ROOT` to a tmp dir (`conftest.py:126`), but the
backend's start path calls `configure_everos_env()`, which deliberately
assigns
rather than defaults that variable (`update_everos.py:228`), so the
spawned
server serves the recorded root while the test's drain helper reads the
tmp
root's sqlite. Worth fixing, but not here.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

Two user-visible behaviour changes, both upstream defaults rather than
choices
made here: foresight extraction stops running unless an install opts in,
and
agent-skill extraction starts running for the first time, which changes
prompt
content and token usage. The `ome.db` `run_record` ring buffer for
`skill_cluster_updated` also grows from roughly 0.8 KB to 14 KB per row
because
`SkillClusterUpdated` now carries a 1024-dim embedding, about 14 MB
instead of
0.8 MB at the default 1000-record cap.

Rollback is `git revert` of this commit plus `uv sync`. The index does
not need
restoring: the schemas are unchanged and `lancedb` `0.33` reads what
`0.34`
wrote, both verified. Installs on glibc below 2.28 are the one case a
revert is
required rather than optional.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…verMind-AI#350)

## Summary

An identity EverOS cannot accept becomes a directory segment on its
write path, so `EverosBackend.start()` rejects it. The rejection carried
the one message a user can act on -- it names the key and the accepted
pattern -- and no surface delivered it as one. Three of the four call
sites fold the raise into `logger.exception`; under the TUI loguru is
file-only, so there an invalid identity turned memory off with nothing
on screen. The fourth, in `import_commands._build_and_run`, is not
wrapped at all, so the `ValueError` escaped as a bare traceback. Where
it was visible it was a traceback, which is not a thing a user reads as
"edit this key".

The state machine then told the user the opposite of the truth.
`start()` died before assigning a state, so `_state` stayed `UNKNOWN`,
which is not in `_NEVER_HAD_MEMORY`; every `store()` after it counted a
dropped write, and the session ended with:

```
1 turn(s) were not written to long-term memory because the memory service was
unavailable.
```

The service was fine. That sends the user to debug a server that never
broke.

**The change.** Catch the `ValueError` in `start()`, give it a
`BAD_IDENTITY` state, and file that state with the other two the user
must fix themselves:

- terminal, so a stray probe cannot promote it to `READY`;
- never-had-memory, so no dropped-write count and no outage wording;
- the message goes to stderr, matching what the unconfigured-LLM and
missing-binary paths beside it already do.

Two things came out of running it rather than reading it:

1. **The message needs escaping.** It quotes the accepted-character
class, and rich reads `[a-zA-Z0-9_.@+-]` as a markup tag and eats it,
leaving the user the pattern `^+$` to match their id against. The test
now pins the full class.
2. **`raven import` had the same defect on its own gate.** It kept the
outage wording and pointed at the server log, which holds nothing about
a config error. It now names the config keys instead.

Before / after, same config (`memory.userId: "bad/id"`), same command:

```
- 1 turn(s) were not written to long-term memory because the memory service was
- unavailable.

+ Long-term memory is off: memory.userId='bad/id' is not accepted by EverOS: it
+ becomes a directory segment on the write path, so it must match
+ ^[a-zA-Z0-9_.@+-]+$ and must not be '.' or '..'.
+ Fix memory.userId / memory.agentId in your config.json, then start a new session.
```

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

Re-run after rebasing onto `d35e398c`, the current `main`.

```
uv run pytest tests/test_everos_backend.py tests/test_cli_import_commands.py \
              tests/test_everos_plugin_discovery.py -q
162 passed

uv run pytest -q
1 failed, 6478 passed, 43 skipped, 13 deselected

uv run ruff check <the four touched files>            All checks passed
uv run ruff format --check <the four touched files>   4 files already formatted
```

The one failure is
`tests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare`. It
is pre-existing on this base: checked out `d35e398c` in a clean worktree
and ran `uv run pytest tests/test_cli_theme.py` there, same failure, 1
failed / 44 passed. It passes in isolation and fails when the file runs
as a whole, so it is order-dependent, and it is unrelated to anything
here. That file is untouched by this change.

Manual check, sandbox `RAVEN_HOME`, `memory.userId: "bad/id"`, `raven
agent -m "hi"`: prints the block quoted above, and the session no longer
ends with the dropped-write line.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

Terminal-only wording plus one new enum member. No config, schema, or
protocol change; an identity that was accepted before is accepted now,
on the same code path. The only behaviour change for a valid setup is
none.

For an invalid identity the turn behaves as it did -- memory off, turn
completes -- and only what the user is told changes. Rollback is
reverting the single commit.

`ServiceState.BAD_IDENTITY` is new. Every consumer was checked:
`raven/cli/import_commands.py` (handled here) and
`tests/test_everos_plugin_discovery.py` (reads `READY` only).
`_may_spawn` allowlists `UNKNOWN` via `_SPAWNABLE_STATES`, so a new
member is non-spawnable without a change.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

Fixes EverMind-AI#271

Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…imit (EverMind-AI#348)

## Summary

`SkillHubClient.search` put the caller's query into the `q` parameter
verbatim. The
deployed hub is fronted by a load balancer (`server: awselb/2.0`) that
answers
`403 Forbidden` -- not `414` -- once a request's query string passes
2048 bytes, and `q`
carries a retrieval query that can be as long as a user's whole message.
So a long
prompt did not search on a prefix; it lost hub discovery outright. The
failure is quiet:
`SkillForgeRouter._safe_search` turns a source error into an empty
candidate list, so the
only symptom is one warning line and skills silently never being found.

Non-ASCII made the real ceiling far lower than it looks. One CJK
character costs 9 bytes
encoded (three UTF-8 bytes, three `%XX` characters each), so the budget
was about 226
characters of Chinese -- under which essentially every non-trivial task
ran with no hub
candidates at all.

The fix trims `q` to the longest prefix that fits, measured in encoded
width rather than
character count, and sizes its budget from what the other params leave
over rather than
assuming `q` is alone. It also logs what was dropped, so a trimmed query
does not read as
a whole one. Measured boundary against the deployed hub: a 2048-byte
query string is
served, 2049 is refused.

Two decisions worth naming, so the next reader does not re-derive them:

- The cap lives in the client, not the caller. The limit is a property
of this endpoint's
wire contract, and the client is the one place every caller passes
through.
- Trimming is per character, not a slice of the encoded form. Slicing
the encoding would
  cut a `%XX` escape in half and produce invalid UTF-8.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

Reproduced against the deployed hub before the fix, bisecting the
boundary: a query
string of 2048 bytes returns 200, 2049 returns 403, with `server:
awselb/2.0` on the
refusal. In CJK terms, 226 characters pass (2045 bytes) and 227 fail
(2054 bytes).

End-to-end through the real client, on the prompt shape that produced
the original 403
(an 860-character mixed Chinese/ASCII query):

```
DEBUG raven.skill_hub.client: hub search query trimmed to fit the 2048-byte
      query-string cap (366 of 860 characters kept)
RESULT: HTTP 200, 10 items returned
```

The same query untrimmed is a 4951-byte query string and returns 403.

```
uv run pytest tests/test_skill_hub_source.py tests/test_skill_hub_client.py \
  tests/test_skill_hub_tools.py tests/test_skill_hub_policy.py \
  tests/test_skill_segment_builder.py -q
-> 97 passed
```

Full suite, and the three new tests proved not to be theatre by
reverting only the
client change and watching them go red:

```
uv run pytest -q -p no:randomly
-> 72 failed, 6572 passed, 36 skipped, 13 deselected

# same command on the base commit, failing test IDs diffed:
-> 72 failed; identical set. 0 introduced, 0 fixed by accident.

# revert client.py only, keep the tests:
-> 3 failed (the 3 new ones), 2 passed
```

The 72 failures are pre-existing on the base commit and unrelated to
this change
(`test_tui_rpc_session.py`, `test_cli_cron_commands.py`,
`test_cli_import_commands.py`,
`test_cli_onboard_commands.py`, and four single-test files).

```
uv run ruff check raven/skill_hub/client.py tests/test_skill_hub_source.py
-> All checks passed!
uv run ruff format --check raven/skill_hub/client.py tests/test_skill_hub_source.py
-> 2 files already formatted
```

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

Disclosed gaps: `make lint` also runs `lint-tui`, which could not run
here because
`eslint` is not installed in this checkout; this change is Python-only
and touches no
TypeScript. `mypy` is not part of this project's dev dependencies, so no
type check beyond
ruff was available. No docs changed: nothing in `docs/` stated a length
contract for this
parameter, so no text went stale.

## Risk

Behaviour change: a query whose encoded form exceeds the budget is now
truncated instead
of being sent whole. Before this change such a request was refused
outright and the
candidate list came back empty, so a truncated query is strictly more
useful than the
status quo -- there is no case that previously worked and now returns
less. Queries inside
the budget are byte-identical to before, which the pre-existing envelope
test pins.

The parameter order in the emitted URL changes (`q` now serialises after
`category` and
`sort`) because the budget is computed from what the other params
consume. Query
parameters are order-independent on this endpoint.

Rollback is reverting the commit; there is no migration, no persisted
state, and no
config surface involved.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…ind-AI#352)

## Summary

LiteLLM fetches its price and context table over the network at import
unless
`LITELLM_LOCAL_MODEL_COST_MAP` is set beforehand. Nothing in this repo
sets it, so the
unit suite reads a remote file as a fixed input. A
`deepseek/deepseek-v4-pro` row
appeared upstream and turned 19 tests red on `main`, on bytes no commit
here had
touched, minutes after the same bytes had passed.

`token_rates` and `resolve_context_window` consult LiteLLM's table first
and only then
the OpenRouter catalogue, which the autouse `_no_openrouter_network`
fixture already
keeps off the wire for exactly this reason. Several tests mock an
OpenRouter catalogue
and arrange for the LiteLLM tier to miss, so the mocked number is the
one under test.
When the new row started answering that lookup first, the mock was never
reached: the
counting transport recorded 0 calls, no disk cache was written, and the
assertions read
live figures instead (a window of 1048576 where the mock said 163840, a
cost of 0.0033
where the mock said 0.00125). The fixture's promise to keep the suite
off the network
covered one of the two doors.

The change is three small parts:

- `conftest` publishes `LITELLM_LOCAL_MODEL_COST_MAP` at module scope,
before any test
module imports LiteLLM. Setting it before first *use* is too late,
because the remote
table is loaded at import. `setdefault`, so a run can still be pointed
at the live
  table deliberately.
- Two assertions genuinely needed a row that exists only remotely.
`minimax/MiniMax-M3`
is the one MiniMax row absent from the bundled table, which does carry
the other
direct `minimax/` models. Both now pin the row, so what they exercise is
the
resolution ladder rather than what MiniMax published this morning. The
key is
`minimax/` and not `minimax-global/` because `_candidates` maps the
plan-billed prefix
  onto the direct one before the table is consulted.
- A third test asserts the offline guarantee itself. Nothing else in the
suite would
  notice it being lost, which is how it was lost in the first place.

Checked and deliberately not changed:

- **`_litellm_price_table` and `_try_litellm_context_window` describe
that table as
"static" and "offline"** (four places in `raven/providers/rates.py`).
Both are wrong
in production, where the flag is never set and the import does reach the
network, and
they are the reason the tests were written believing it. Correcting them
means
touching the provider hot path, and whether production should read a
pinned table
instead of a fetched one is a behaviour question with its own
trade-offs: a bundled
table is reproducible but goes stale, and it lacks rows the fetched one
has, which is
the same gap the MiniMax pin works around. Left for a change that can
weigh it
properly, because `main` is red right now and this is the smallest
surface that clears
  it.
- **The `allow_fetch=False` tier is still order-dependent.**
`_try_litellm_context_window` returns early only while LiteLLM is absent
from
`sys.modules`, so whether that tier is shut depends on which test
imported LiteLLM
first. Pinning the table makes those cases deterministic, which is what
this change is
  for, but it does not make the contract they assert true.

## Type

- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other -- tests only

## Verification

Full suite on the base commit and on this branch, same machine, failing
test IDs
diffed rather than counts compared:

```
base   (0e544ec):  100 failed, 6379 passed, 46 skipped, 13 deselected
branch:              81 failed, 6399 passed, 46 skipped, 13 deselected

introduced: 0
fixed:     19   (exactly the set that went red on main)
```

The two files that exercise the ladder, with no flag on the command
line, which is what
proves the `conftest` placement is early enough:

```
pytest tests/test_provider_rates.py tests/test_agent_loop_usage_sink.py -q
-> 71 passed
```

Not vacuous: the same two files with
`LITELLM_LOCAL_MODEL_COST_MAP=False`, which
pre-empts the `setdefault` and so removes the guarantee:

```
-> 20 failed, 51 passed
```

Twenty are the nineteen original failures plus the new test that exists
to notice
exactly this.

Repo gate and lint:

```
python scripts/check_commit_messages.py 0e544ec..HEAD   -> exit 0
ruff check raven tests scripts                           -> All checks passed!
ruff format --check raven tests scripts                  -> 844 files already formatted
```

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

Disclosed gaps. The 81 failures that remain are present on the base
commit too and are
unrelated to this change (58 in `test_tui_rpc_session.py`, the rest
across the CLI cron,
import, onboard and TUI command suites); they are environment-dependent
on this machine
and CI does not reproduce them, which is why the red count on CI was 19
and not 100. The
full suite segfaults at interpreter finalization on base and branch
alike, before and
after. `make lint` also runs `lint-tui` and `lint-bridge`, which could
not run here
because their Node toolchains are not installed in this checkout; this
change adds no
TypeScript. No docs changed: nothing outside `tests/` mentions this
flag, which is the
same grep that confirms production never sets it.

## Risk

Test-only. No file under `raven/` changes, so no runtime behaviour
moves: in production
LiteLLM still fetches its table exactly as before, and this only stops
the suite from
asserting against it.

The trade-off is that the suite now reads a table pinned to whatever
LiteLLM bundles, so
a genuine upstream correction to a window or a price no longer reaches
these assertions.
That is the intended direction -- a unit test should fail because the
code changed -- but
it means the numbers in them describe LiteLLM's bundled data rather than
a live vendor
claim, and a LiteLLM upgrade can still move them.

Two existing tests changed meaning and are worth a reviewer's eye:
`test_a_plan_billed_provider_still_reports_a_window` and
`test_the_window_those_families_report_is_the_vendors_own` previously
passed by reading a
live row, and now pass by reading a pinned one.

Rollback is reverting the commit; the suite returns to reading the live
table, with the
failure mode described above.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues\n\nFixes EverMind-AI#358

Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…rMind-AI#355)

## Summary

A colleague reported the TUI composer feeling laggy while typing; it did
not
reproduce on a fast local terminal with short English input. Two causes,
and
they amplify each other.

**Typing was being read as a paste.** Typing and pasting are the same
bytes on
stdin, so the parser merged any multi-character run into one nameless
keypress
and the composer treated it as an unbracketed paste held behind a 50ms
debounce. SSH, tmux and a blocked event loop all coalesce keystrokes, so
continuous typing kept resetting that debounce and the input box stalled
for
the whole burst. A CJK input method commits several characters at once,
so it
took this path on almost every word.

The fix stops guessing from length. A plain byte run is split into one
keypress
per code point, and bracketed paste (DEC 2004) becomes the paste signal
it was
always meant to be. App asks the terminal with DECRQM whether the mode
actually
took, riding the query batch that already carries XTVERSION, and the
parser
records the answer in its own state. Confirmed: every unmarked run is
typing.
Not confirmed: only runs that could not be a paste line - free of
control bytes
and no longer than a typing burst (32 characters) - split, so multi-line
paste
still stays whole on terminals without the markers.

Three input bugs shared that root cause and go away with it: an
auto-repeated
backspace inserted literal DEL characters instead of deleting, a return
arriving in the same read as the text before it was swallowed without
sending,
and a control key arriving alongside text inserted a literal control
character.

**Wide characters could never take the fast-echo path.** Fast echo
writes a
character straight to the terminal and defers the React update to the
next
frame, so a keystroke that takes it costs no render. Its guard required
display
width to equal string length, which a CJK character or an emoji can
never
satisfy, so every wide character forced a full tree reconcile. The
terminal
advances two cells for a wide character on its own, so the guard now
only holds
what the terminal cannot: a single grapheme, at the end of a single line
that
still has room.

The two halves ship together on purpose. Without the second, a
two-character
IME commit would trade one delayed render for two immediate ones.

Also memoizes the `useVirtualHistory` return value. `useMainApp` feeds
it into
the `appTranscript` memo, so a fresh object identity on every render
re-rendered the whole transcript on every keystroke - the amplifier that
made
long sessions worse.

Note for review: this is the first change to the ink fork's
implementation in
the repository's visible history (four earlier commits touched only its
README,
export surface and lockfile). Its README asks for the same test and
review bar
as first-party code, so `parse-keypress.ts` is the part worth reading
closely.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

Run from `ui-tui/`, on this branch rebased onto `main` at 0e544ec,
after a
clean `npm ci`:

- `npm test` - 90 files, 1031 tests passed, 0 failed
- `npm run type-check` - clean
- `npm run lint` - 0 errors, 22 warnings, identical to the pre-change
baseline
(compared warning-by-warning against a clean checkout of `main`; only
line
  numbers moved)

Three load-bearing new tests were mutation-checked: the implementation
was
broken on purpose and each test was confirmed to fail, then restored.
One of
them silently passed on the first attempt because component tests load
the
bundled `packages/hermes-ink/dist` rather than its source; it was
re-checked
after rebuilding.

Manual, in `raven tui --dev` after rebuilding the ink bundle: holding
backspace
deletes instead of inserting `^?`; typing a sentence and pressing return
immediately sends it; CJK input keeps up with typing; a
several-hundred-line
paste still collapses into a single placeholder; a dropped file path is
still
recognised.

New coverage:

| File | Tests | Covers |
| --- | --- | --- |
| `parse-keypress.test.ts` | 28 -> 47 | split rules in both modes, five
DECRQM reply statuses, paste content untouched |
| `textInputFastAppend.test.ts` | 10 | wide characters and emoji
accepted; line end, newline, column limit, multi-grapheme and zero-width
rejected |
| `textInputTypingBurst.test.tsx` | 2 | end to end, a coalesced run from
real stdin through the parser into the composer |
| `virtualHistoryIdentity.test.tsx` | 2 | identity survives an unrelated
re-render, changes when the item list does |

Three existing tests in `parse-keypress.test.ts` were updated. They
asserted
that a text run arrives as a single key, which is the shape this change
replaces; they now assert the intent instead - every event is a key and
their
sequences join back to the original text. That also checks each event's
kind,
which the old form did not.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

User-visible behaviour changes, all in the composer:

- typing no longer stalls behind the paste debounce;
- CJK and emoji no longer force a full re-render per character;
- backspace auto-repeat, return-after-typing and control-key-with-text
now do
  what they say instead of inserting literal control characters.

The regression surface is paste. On a terminal that confirms DEC 2004
nothing
about paste handling changes: markers still delimit it and the parser
already
reassembled marked pastes across reads. On a terminal that does not
confirm it,
short printable runs now arrive as typing, which is why the 32-character
threshold is there - it keeps long single-line pastes whole so
placeholder
collapsing and dropped-path detection still work. A paste of a short
single-line string on such a terminal is the one case that changes: it
inserts
character by character instead of going through the paste handler.

Rollback is a revert of the two commits; they touch no state, no
protocol and
no persisted format.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

---------

Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…EverMind-AI#362)

## Summary

Add an end-to-end trajectory workflow for capturing, packaging,
reporting, replaying, and permanently guarding Raven agent runs.

- Give every tracing span an attempt id, with explicit multi-turn
attempt scopes and a single-turn fallback. Add append-only
pass/fail/infra verdicts, trajectory pins, rotation-transparent lookup,
and canonical attempt resolution.
- Add `raven trajectory` commands to list, label, pin, save, report,
replay, and minimize attempts. Saved bundles are self-contained and
include rewritten artifact references, session history when available,
verdicts, and a manifest.
- Add three-layer report redaction: exact replacement of known config
and environment secrets, pattern-based credential removal, and a
residual scan for human review. Reports are produced as local tarballs
through a pluggable uploader interface.
- Add deterministic replay through the live agent harness with recorded
model replies and stubbed tool results. Replay never executes real
tools, uses a temporary workspace, suppresses its own tracing, and
reports strict or warning-mode divergences under narrow normalization
rules.
- Add minimized and redacted trajectory cassettes, a YAML assertion DSL,
and pytest discovery for permanent harness regression cases. Assertions
can require a specific first divergence and verify the fixed live
message or tool request at that point.
- Harden all externally derived paths and replacement directories,
including canonical ids, bundle output paths, cassette destinations,
artifact traversal, and escaping symlinks.
- Change streamed response aggregation to preserve a missing upstream
finish reason as `unknown` instead of fabricating `stop` or
`tool_calls`, keeping recorded trajectories faithful to the provider
response.

## Type

- [ ] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `uv run --frozen pytest tests/ -q --ignore=tests/integration`: 6862
passed, 33 skipped, 13 deselected, and 1 unrelated existing
theme-rendering test failed. The branch does not modify that test, the
theme implementation, or dependency manifests.
- `uv run --frozen pytest
tests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare -q`:
the same unrelated theme-rendering failure reproduced in isolation.
- `uv run --frozen ruff check .`: passed.
- `uv run --frozen ruff format --check .`: 937 files already formatted.
- `make check-large-files`: passed.
- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- The new CLI and trajectory formats are additive. Existing spans gain
an `attempt.id` attribute, and readers retain compatibility with older
spans by falling back to the trace id.
- Report and cassette payloads can contain complete model and tool text.
Redaction runs on copies, excludes unverifiable binary files, records
its actions, and requires residual review before sharing or committing
data.
- Replay is isolated from real tool execution, the real workspace,
session persistence, and tracing. Its normalization deliberately covers
only values regenerated on every run so real behavior changes remain
visible.
- The only intentional existing-path behavior change is the `unknown`
fallback for a missing streamed finish reason. Production consumers were
checked for dependence on the previous synthesized values.
- Rollback is to revert this PR. Existing trajectory state files and
directories are additive and can remain unused after rollback.
- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

---------

Co-authored-by: 江国庆 <guoqingjiang@deepglint.com>
Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Co-authored-by: zhanghui <huizhang1995@gmail.com>
…urn (EverMind-AI#361)

## Summary

Two presentation defects in the TUI chat transcript. They ship together
because both
change what the virtualized transcript has to reserve rows for, and
reviewing the row
arithmetic once is safer than twice.

**The cover was never a screen.** It is transcript row 0 -- a message
with `kind: 'intro'`
that `appLayout` renders as the wordmark plus the session panel -- and
`capHistory` pinned
it at index 0, so it was exempt even from the history cap. Measured
against the real
renderer it occupies 35 terminal rows, while `estimatedMsgHeight`
guessed 9. So the first
answer was appended *below* a full screen of cover art instead of
replacing it, and
scrolling back up always found it again.

`hideIntroAfterFirstTurn` drops it from the view once a `user` or
`assistant` row exists.
The row itself stays in `historyItems`, because the late `session.info`
event patches
itself onto that row, and `/export` and the slash handlers read the
session's own list
rather than whatever is on screen. The trigger is deliberately a turn
and not "any other
row": startup notices go through `sys()` as `role: 'system'` and slash
output is
`kind: 'panel'`, so gating on those would mean a host that warns about
its credentials on
boot never shows a cover at all.

Both the rows and the layout's copy come from the one filtered array.
`appLayout` compares
`row.index` against indices it derives from `transcript.historyItems`,
so filtering one and
not the other would move the inter-turn separator and the todo panel one
row off.

**The person's own message now renders on a filled background,** so a
turn reads as an
inserted card rather than one more line of prose. `userBg` is a curated
literal per tier
and scheme, the way the rest of the reduced-tier palette is carried, and
is skinnable
through `ui_user_bg`. Below 256 colors there is no shade between black
and brightBlack, so
the fill is skipped and the prompt chevron carries the row alone. The
two padding rows are
drawn at every tier so `estimatedMsgHeight` can reserve a row count
without reading the
terminal's color capability; it now reserves four rows for a user
message rather than two.

One testing note worth carrying forward: `ink-testing-library` brings
its own reconciler
and silently drops both foreground colors and background fills, so a
fill is only
observable through `renderSync` writing real escape codes to a stream.
An early version of
these tests passed against nothing.

## Type

- [ ] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

Mixed in practice: the cover half is a fix, the filled block is new
behaviour.

## Verification

Run in a clean worktree at this branch, with `packages/hermes-ink` built
from this base
rather than borrowed, so the render assertions exercise the Ink this
branch ships:

- `npm run type-check` -- clean.
- `npx eslint src/` -- 0 errors, 22 warnings, all pre-existing.
`useMainApp.ts` carries two
of them before and after the change (same rules, shifted 12 lines by the
inserted
comment), confirmed by linting the pristine file from `main` and
diffing.
- `npx vitest run --no-file-parallelism` -- 87 files, 996 passed, 13
skipped, 0 failed.
- `scripts/check_commit_messages.py main..HEAD` -- exit 0.
- Each of the three mechanisms reverted **in isolation** to prove the
tests are not
theatre: neutering `hideIntroAfterFirstTurn` reddens the two cover
tests; removing only
the `backgroundColor` prop while keeping the padding reddens the fill
test; changing the
  reserved rows from four back to two reddens both height tests.

Suites that exercise the changed code directly: `messages.test.ts` (the
filter, and the
block rendered through `renderSync`), `virtualHeights.test.ts` (the
reserved rows),
`theme.test.ts` (the token per tier, and the fill gate).

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

`ui-tui/CONTEXT.md` gains **Prompt Block** and **Cover**, since both are
named in the code
and neither existed as a documented term.

## Risk

Visible change to every chat transcript: a user message grows from three
rows to five (two
of them the filled padding), and the cover stops being visible after the
first turn.
Nothing is removed from the session -- the cover row is still in
`historyItems`, so
`/export` and session save are byte-identical.

Rollback is the revert of this one commit; there is no migration, no
persisted state, and
no wire-format change. A skin that dislikes the shade can set
`ui_user_bg` without a code
change.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

Known gaps, disclosed rather than left for review to find:

- The index alignment between `virtualRows` and the layout's
`historyItems` is held by
construction and by a comment, not by a test; there is no existing
harness that mounts
  `useMainApp` against a gateway.
- `theme.ts` cites `scripts/gen-color-palettes.mjs` as the provenance of
the reduced-tier
palettes and a `docs/tui-color-problem/tokens.md` for the tier-2 values.
Neither holds
today: that script's embedded source palette is a stale green one, its
algorithm maps
`#161b22` to `ansi256(16)` where the file has `ansi256(234)`, and the
doc does not exist.
The values are hand-curated, and the ones added here follow the existing
neighbours. Both
  stale comments are left alone rather than widen this diff.

## Related Issues

N/A

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Co-authored-by: zhanghui <huizhang1995@gmail.com>
EverMind-AI#354)

## Summary

`ask_user` advertised things it did not do, and a batch cost the user
more than
one wait. This closes both, and gives the prompt the fields it needs to
render a
batch honestly.

- **Two schema fields nothing read.** `multiple` and `custom` were
declared per
question and never passed to the broker, so a model asking for a
multi-select
got a single-select and never learned why. Both are removed.
Multi-select is
deliberately not implemented here: it would need a new answer syntax on
the
chat-channel surface, which has no dialog to hold a multi-select, and
that is
  a larger decision than removing a field that never worked.
- **One deadline per call, not per question.** A batch was N round-trips
with N
independent timeouts, so a three-question call could hold a lane for
three
times the surface's wait. The call now shares one budget, and a spent
budget
stops the batch instead of opening a fresh wait on every question left.
- **The budget is configuration.** `tools.ask_user.timeout` (600s, must
be
positive) reaches the tool through `AgentLoop`, which is where the
config
already is. It deliberately does not read config at the transport: the
TUI
builds its broker before any config is loaded, and reading it there put
a
  schema-validation failure in the path of the RPC server coming up.
- **Calls that would waste the user's time are rejected before
rendering**, with
  a message that steers the retry: more than four questions, a duplicate
question text, or a question with exactly one option, which is not a
decision.
A duplicate option label is a typo with one obvious reading, so it is
deduped
  rather than rejected. Zero options remains a free-form question.
- **`header` and `recommended`.** A short chip label, and a 0-based
index into
`options` naming the option the agent would pick. When nobody answers,
the
text the model reads now names that option; before, the only signal was
that
  no answer arrived.
- **A recommendation is resolved against the options as submitted.** The
index
counts what the caller sent, but the list was deduplicated before the
index was
resolved against it. With `["a","a","b","c"]` and `recommended: 2` the
caller
means `"b"` while the broker was handed `"c"`, so the surface marked the
wrong
row and a timeout told the model the wrong intended fallback; an index
past the
deduped length (`3` on that list) failed the range check and the
recommendation
was dropped without a word. The label is now resolved before dedup
narrows the
list. Dedup keeps every distinct label, so the resolved label is always
still
  one of the choices the surface can mark.
- **An undeliverable question is reported, not dropped.** A question for
a
conversation with no live source was dropped with a log line the broker
could
not see, so the round-trip waited out its whole budget on a question
nobody
would ever see. The channel adapter now raises
`QuestionUndeliverableError`
  and the broker fails safe at once.
- **The prompt renders the batch.** Position in the call, the questions
still to
come, the recommended option marked, and the remaining budget counting
down.
Tab attaches a note to a selection, so an answer no longer has to be
either a
choice or free text. On a chat channel the position rides in the message
text.
- **The countdown renders whole seconds.** `timeout_s` is a
remaining-time
subtraction, so a 90-second budget arrives as `89.99999912502244` and
the
prompt decrements that same fractional value once a second. The
formatter
passed it straight through, so the line read `1m 29.99999912502244s` on
every
tick and widened the prompt that the padding exists to keep steady. The
value
is now ceiled before the branch is chosen, since ceiling inside the
branches
  renders a ceiled `59.7` as `60s` rather than `1m 00s`.
- **The `Other` row answers to its own number.** This one predates the
rest of
the branch. The options list draws `Other` as one more numbered row, but
the
quick-pick handler bounded itself at the number of real choices, so the
last
row was numbered and unreachable -- and worse than inert: the keystrokes
that
followed were swallowed by the options handler, so the next Enter
submitted
whichever option was still highlighted. Someone who thought they were
typing a
free-form answer sent a selection they never made. Its number now opens
the
text input, which is what Enter on that row already did, and the hint
names
  the range it can take.

The wire payload gains `header`, `recommended`, `timeout_s`, `index`,
`total`
and `batch`. `clarify.request` is a notification and is not part of
`rpc-schema/openrpc.json`, so no generated artifact changes; `npm run
lint:rpc`
confirms `generated.ts` is still in sync. `deep_research` shares the
same broker
and passes none of the new fields, so its prompt is unchanged except
that it now
shows the countdown, which was always the real deadline.

`timeout_s` stays fractional on the wire deliberately: it is also what
the model
is told about the budget, and rounding it at the source would change
that value
to serve a display concern. The formatter is the layer with a width to
protect.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

Mostly fixes; the prompt affordances and the config field are additive.

## Verification

Every new test was watched failing before the code that makes it pass.

Rebased onto `22b71765`, which collapsed the earlier `Merge branch
'main'` commit
into a linear six-commit history. The base gained EverMind-AI#361 and EverMind-AI#362; the
only file
this branch and those commits both touch is `raven/agent/loop/main.py`,
and the
replay was clean. `git merge-tree --write-tree` needs git 2.38 and this
box has
2.34.1, so the pre-push check was that file-overlap table plus a full
re-run at
the new head rather than a dry-run merge. Everything below is from the
new head,
not carried forward.

```
python -m pytest tests/ -q
  82 failed, 6576 passed, 47 skipped, 13 deselected

# same suite on the new base 22b7176, in a separate worktree:
  82 failed, 6545 passed, 47 skipped, 13 deselected

# full failing-ID lists from both runs, sorted and diffed:
diff branch_fail.txt base_fail.txt   -> identical (0 introduced, 0 fixed)
```

The 82 failures are inherited: the base fails the same 82 IDs on this
box, and the
sets are identical rather than merely the same size. They are
`test_tui_rpc_session.py` (58), `test_cli_cron_commands.py` (7),
`test_cli_import_commands.py` (5), `test_tui_commands_error_codes.py`
(3),
`test_cli_onboard_commands.py` (3), and 6 more spread across 5 files.
The +31
passing tests are the ones added here.

Suites that exercise the changed code directly:

```
python -m pytest tests/test_ask_user_tool.py tests/test_question_broker.py \
                 tests/test_cli_gateway_commands.py tests/test_config_schema.py -q
  81 passed

npm run test -- --run src/__tests__/clarifyPrompt.test.tsx
  11 passed

npm run type-check   -> clean
npm run lint:rpc     -> OK: generated.ts in sync

python -m ruff check raven tests           -> All checks passed!
python -m ruff format --check raven tests   -> 849 files already formatted
python -m scripts.check_commit_messages github/main..HEAD -> exit 0
scripts/check_large_files.py                -> exit 0
```

Beyond the suites, the tool was exercised over the real path -- a real
`ToolRegistry` (so `cast_params` and the schema validator run), a real
`QuestionBroker`, and answers delivered through the real
`clarify.respond`
handler on a real `Dispatcher`:

- the three rejections emit **zero** prompts, so a malformed call costs
the user
nothing, and the steer text reaches the model intact through the
registry;
- a batch answered by a responder taking 0.25s per question is handed
`10.0000s`, `9.7495s`, `9.4991s` -- each question inherits what the last
one
  left;
- a batch whose budget is spent emits **one** prompt, not one per
question;
- an undeliverable question returns in `0.001s` against a 30s budget.

`test_registry_dispatch_and_the_real_clarify_respond_route` was added
from that
exercise, because no committed test drove the registry before. It was
proved
non-vacuous by renaming the `recommended` keyword the tool passes the
broker and
watching it go red.

The two review fixes were each proved the same way:

- The recommendation cases were run against the deduped resolution and
failed
with `assert 'c' == 'b'` (wrong label) and `assert '' == 'c'`
(recommendation
  dropped), then passed once the resolution moved ahead of dedup.
`test_out_of_range_recommended_index_is_ignored` passes unchanged -- it
has no
  duplicates, so it never encoded this.
- The countdown cases were run against the pass-through formatter and
failed on
`expected ... to match /1m 30s/` and `/1m 00s/`, covering both the
fractional
  payload and the `59.7` boundary that ceiling creates.

`prompts.tsx` and `clarifyPrompt.test.tsx` are reported by Prettier, and
were
before this branch touched them: the hunks it wants are at lines this
branch does
not change (a JSX ternary and a `renderSync` call), so they are left
alone rather
than reformatted into this diff.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

`docs/Proactivity-Implementation.md` documented the two removed fields
and the
old narrow payload, so that paragraph is rewritten.
`ui-tui/dist/entry.js` is
gitignored, so testing the prompt by hand needs
`npm run build --prefix ui-tui` first.

## Risk

User-visible changes:

- A model sending five questions, a duplicate question, or a one-option
question
now gets an error instead of prompting the user. The error text tells it
what
  to do instead.
- A batch that used to get a fresh timeout per question now shares one.
A slow
user answering question 3 of 4 past the budget gets the rest recorded as
  unanswered rather than being asked.
- The prompt shows a countdown where it showed none, including for
  `deep_research`, and that countdown now reads in whole seconds.
- A recommendation sent as an index past the deduped option count used
to be
dropped silently and now resolves. That is what the schema promises, but
it is
  a real change for a caller that had been sending such an index.
- Typing the `Other` row's number now opens the text input instead of
doing
  nothing.

Rollback is the six commits; nothing is persisted and no format changes
on disk.
The config field defaults to the previous 600s, so an untouched config
behaves
as before.

Checked and deliberately not fixed, to keep this diff to one subject:

- `QuestionBroker.await_question` still returns its default on
`asyncio.CancelledError`, which swallows an outer cancellation. It sits
beside
the handler added here, but changing it means auditing how the agent
loop
  reacts to a cancelled turn.
- The gateway's inbound gate calls `pending_req(cid)` and then
`reply(...)`. If
the question times out between the two, the message answers nothing and
starts
no turn. `reply` already returns a bool, so using it as the condition
would
  close this.
- The registry's schema validator does not implement `maxItems`, so the
cap in
the schema is advisory to the model and `_prepare` is what enforces it.
That
  is deliberate -- the model gets the steer text rather than a generic
validation error -- and a test pins the two statements of the cap
together so
  they cannot drift.
- `ApprovalPrompt` in the same file hardcodes `1-2 quick pick`, which is
correct
for its two fixed options and has no `Other` row, so it is not the same
bug.
- `approvalRemainingSeconds()` feeds the approval countdown in the same
file and
is already `Math.max(0, Math.ceil(...))`, so it needs nothing here. It
is also
where the `ceil` convention comes from: the clarify countdown now rounds
the
same direction as the approval one rather than inventing a second rule.
The two
are still not made to share a formatter -- one takes an absolute
deadline and
  the other a remaining duration.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

No new secret, no new surface, and no asset moved onto an
unauthenticated one.
The new config field is a timeout.

## Related Issues

N/A

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…lipboard path lying (EverMind-AI#360)

## Summary

A TUI that enables mouse tracking owns the drag, so the terminal never
builds a
native selection and its own copy shortcut has nothing to copy.
Copy-on-select is
what makes a transcript selection copyable at all -- but the
subscription bailed
out on `!isMac`, so on Linux and Windows a drag highlighted text and
copied
nothing. This lifts that gate, and fixes what was found while making the
result
observable.

**Copy-on-select on every platform.** The subscription moves out of
`useMainApp`
into `subscribeCopyOnSelect()`. Inlined in a hook that needs a live
gateway to
start, none of its four guards could be tested; each now has a case, and
each was
checked by removing only that guard and watching the suite go red. The
read of the
bus state stops being an unchecked cast: the ambient `useSelection()`
declaration
types it as `unknown`, so the module narrows it instead.

**The copy is reported to the transcript.** Nothing on screen changes
when a drag
ends, so a line is written once a clipboard path has actually taken the
text. The
callback fires on a non-empty result only, since
`copySelectionNoClear()` resolves
to an empty text when nothing reached the clipboard. The first report of
a session
carries the path caveat, later ones stay terse: OSC 52 is the one path a
terminal
can still refuse, and the first copy is when a user is looking for the
reason a
paste came up empty.

**The reported path comes from the write, not from the environment.** A
path
predicted from env state cannot be right in general, because the
environment does
not record what happened: inside tmux a `load-buffer` that fails falls
through to
raw OSC 52 and leaves `TMUX` set exactly as the case that worked. With a
stale
tmux socket the bytes went out as OSC 52 while both callers said "copied
to the
tmux buffer", pointing the user at a `set-clipboard` setting that was
never
involved. `setClipboard()` already computed the three facts that decide
this --
whether native was attempted, whether the buffer loaded, whether a
sequence was
emitted -- so it now reports which one took the text, and null when none
did. The
value travels with the copied text out of `copySelectionNoClear()` and
`copySelection()` to the two call sites that display it. The predictor
is deleted
rather than left exported from the package: after the rewiring it had no
callers,
and a helper that can name a path the data did not take is a defect
waiting for
the next caller. Its env-matrix cases were assertions about the
predictor itself,
so what replaces them drives the real `setClipboard()` with `tmux`
stubbed,
including the failed-load fallback.

Reporting an observed path also has to wait for the observation. On
Linux the
native tool is discovered by a probe, and `copyNative()` answered the
first call
before that probe settled -- a display server means a tool could exist,
not that
one does. Naming the path from that optimism claimed a native write on a
machine
with `DISPLAY` and no `wl-copy`, `xclip` or `xsel`, and the same call
reported
failure once the probe finished. The first call now answers with the
probe's own
result. It is still started before the tmux await, so the probe runs
alongside
`load-buffer` rather than ahead of the report, and later calls still
answer
synchronously from the cache. The copy never ran ahead of that probe
either --
the tool is spawned inside it -- so only the report did.

**The path caveat is scoped to a session, not to the process.** The
first copy of
a session carries the caveat and later ones stay terse, but the flag
holding that
lived in a hook that outlives the session: `newSession()` and
`resumeById()`
replace `ui.sid` without remounting `useMainApp`, so every session after
the first
opened with the terse line and the user never learned an OSC 52 paste
can come up
empty. The tally moves into a reporter keyed on a session identifier, so
the state
and the thing it is scoped to live together and the boundary is
unit-testable.
The hook reads the sid through `getUiState()`, which keeps a session
change from
tearing down the bus subscription.

**The report must not overclaim.** OSC 52 hands bytes to the terminal
and the
terminal decides whether to keep them, and nothing in the write path
caps or
chunks the payload. Measured on the real code: one 200x50 viewport of
CJK is a
40 KB escape sequence, and a selection dragged through 2000 rows is 536
KB in a
single sequence. Terminals drop an oversized sequence without a word,
and
`setClipboard()` reports success for all of it because bytes were
written to
stdout. So that path reports what it sent; native and tmux, which really
did
write a clipboard, still say copied. The count was UTF-16 code units,
which
reads three emoji as six characters and a combining accent as two, so it
is
counted by grapheme now. CJK was already right, being one code unit per
character.

**The documented env knobs were dead.** The OSC 52 override and the
clipboard
debug switch were readable only under the upstream `HERMES_TUI_` names,
while
every knob this project documents uses `RAVEN_TUI_` and `/copy`'s own
failure hint
names `RAVEN_TUI_FORCE_OSC52` and `RAVEN_TUI_DEBUG_CLIPBOARD`. Following
that hint
changed nothing. Both spellings are now read, `RAVEN_TUI_` first, with
the
`HERMES_TUI_` names kept as aliases so an environment that worked before
still
works. The debug switch moves behind `clipboardDebugEnabled()` so its
four call
sites cannot drift apart.

## Type

- [x] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

Two boxes: the platform gate is the feature, the clipboard-path and
env-name
defects are fixes found while making it observable.

## Verification

Run from `ui-tui/`, on Linux, node 22, at branch head after the rebase.

```
npx vitest run --no-file-parallelism
  Test Files  91 passed (91)
  Tests  1066 passed | 13 skipped (1079)

npx tsc --noEmit -p tsconfig.json
  clean, exit 0

npm run lint
  22 problems (0 errors, 22 warnings)

python3 -m scripts.check_commit_messages github/main..HEAD
  exit 0
```

The suite is run with `--no-file-parallelism` deliberately: the ink
render tests
fail under default worker parallelism at this suite size, and no CI job
runs them.

The 22 lint warnings are the baseline, not new: 22 before the branch and
22 after,
none of them in a file this branch touches.

Every file this branch touches passes `prettier --check`. Two of them
did not at
first, and that was this branch's doing rather than the base's: dropping
a name
from the `@hermes/ink` import and adding a wide function signature left
both
inside the print width while still wrapped. They are formatted in their
own
commit. The files already unformatted on `github/main`, `chatStream.ts`
among
them, are left alone rather than reformatted into this diff.

Suites that exercise the changed code directly:
`src/__tests__/copyOnSelect.test.ts` (12 cases),
`src/__tests__/clipboard.test.ts` (30 cases),
`packages/hermes-ink/src/ink/termio/osc.test.ts` (39 cases).

An environment gap that made the first pass of this verification
worthless, since
it would silently affect anyone reviewing from a worktree:
`ui-tui/node_modules`
here was a symlink to another checkout's, so `node_modules/@hermes/ink
->
../../packages/hermes-ink` resolved against the symlink target and
landed on that
other checkout's copy of the package. `tsc` and the dist build therefore
read an
unmodified `hermes-ink` while the app code under test was the edited
one. Fixed by
giving the worktree a real `node_modules` whose `@hermes/ink` points at
its own
package, confirmed with `tsc --listFiles` naming the edited `ink.tsx`,
then
rebuilt and re-ran everything. Every number above is from after that.

New tests were proved load-bearing rather than assumed:

- Each of the four guards in `subscribeCopyOnSelect()` was removed on
its own and
  the suite watched go red (1, 1, 2 and 2 failures), then restored.
- The three cases that assert a copy did *not* happen are each paired
with a
positive assertion on the same subscription, because a bus that was
never wired
  up satisfies the negative half by itself.
- The path-reporting cases were checked by reverting the derivation to
the deleted
predictor's rule and watching the failed-load case report `tmux-buffer`
again.
- The Linux first-probe case was checked by reverting `copyNative()` to
its
optimistic `return true` and watching it fail on `success: true, path:
'native'`
where it now asserts `success: false, path: null`. Establishing that
this one
was introduced here rather than inherited took running the same scenario
against `github/main`, which reported `path: 'osc52'` for it: `success:
true`
  with nothing written predates the branch, the native claim does not.
- Of the three session-scoping cases, one is load-bearing: reverting the
reporter
to a process-wide flag fails "spends the caveat once per session". The
other two
-- a session returned to, and two reporters not sharing a tally -- pass
with the
flag as well, and are there to pin the property against a later
refactor. Said
  plainly rather than implying all three prove the fix.

One pre-existing assertion is flipped rather than kept. It pinned the
terse
copy-on-select line to "copied" on the OSC 52 path, which is the
overclaim this
branch removes, so the assertion encoded the defect instead of the
behaviour
worth holding.

Rebased onto `22b71765`. The base gained EverMind-AI#361, EverMind-AI#362 and EverMind-AI#355; the only
file this
branch and those commits both touch is `ui-tui/src/app/useMainApp.ts`,
where EverMind-AI#361
reworked the cover and prompt block while this branch replaced the
copy-on-select
effect in the same hook. The replay was clean and the whole verification
above was
re-run at the new head rather than carried forward. `git merge-tree
--write-tree`
needs git 2.38 and this box has 2.34.1, so the check was the
file-overlap table
plus the re-run, not a dry-run merge.

Manual check of the rendered artifact, since `raven tui` runs the
prebuilt
`ui-tui/dist/entry.js` and nothing rebuilds it automatically: `npm run
build`,
then confirmed the new strings are in `dist/entry.js`.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

No doc change. `CONTEXT.md` has no clipboard or selection entry, and
copy-on-select was already the term in use in the vendored fork, so
nothing new is
coined. The `/help` hotkey table is deliberately untouched:
select-then-copy is
ordinary terminal behaviour and does not need advertising.

## Risk

Behaviour changes for users who are not on macOS: a drag now writes the
clipboard
and adds a transcript line. On macOS nothing changes except the wording
of that
line. `/copy` keeps its shape and gains the path the write actually
took.

One API change inside the vendored fork: `Ink.copySelection()` and
`copySelectionNoClear()` return `{ text, path }` instead of the copied
string, and
`getClipboardPath()` is gone from the package's exports. Internally
`copyNative()`
may now answer with a promise on Linux's first copy, which
`setClipboard()` awaits
after starting the tmux write; no caller outside that function sees it.
Both are consumed only
within this repo, and the ambient declaration, the app interface and the
two call
sites move with them.

Rollback is per concern. Reverting the feature commit restores the
platform gate
and leaves the fixes, which stand on their own. Reverting all of them
returns the
files to their base state; no data, config or on-disk format is
involved, and no
migration exists to undo.

Checked and deliberately not fixed:

- The OSC 52 write has no size cap and no chunking, so a large selection
is
handed to the terminal whole and silently dropped past whatever that
terminal
tolerates. This is why the OSC 52 path reports what it sent rather than
claiming a copy. Capping or chunking it means changing the vendored
write path
and picking a threshold per terminal, neither of which belongs in this
branch.
- `Ink.copySelectionNoClear()` calls `getSelectedText()` outside its own
`try`, so
a throw there rejects the promise. The base already left that rejection
unhandled at a bare `void` call, and adding a `.then()` does not change
it.
Fixing it means restructuring a vendored method this branch has no other
reason
  to touch.
- The effect's dependency array gains `sys`. It is provably stable
(`useCallback`
over `appendMessage`, itself `useCallback` with `[]`), so the
subscription is
not re-created in practice. If it ever were, the version de-dupe would
reset and
one extra line could be written; the path caveat would not repeat,
because that
  tally now lives in a ref that survives the effect.
- Running `/copy` before the first drag of a session shows the path
caveat twice,
once from each lane. Sharing that state between an explicit command and
an
  automatic one to save a duplicated sentence is not worth the coupling.
- Two narrative comments in `copyOnSelect.test.ts` were reduced rather
than
removed: each kept the constraint it was wrapped around (that the
platform case
can only mean something on a non-macOS runner, and that an empty result
is the
whole signal that nothing reached the clipboard) and lost the "defect
this
closes" framing. Reading section 1.1 as rejecting the task-context
wrapper
rather than the constraint underneath is a judgement call, flagged as
one.

The selection bus is faked in the new tests. The real one returns no-ops
outside a
fullscreen Ink instance bound to a TTY, so there is no way to drive it
from
vitest; the fake mirrors the ambient `useSelection()` contract,
including
`getState(): unknown`, which is why the narrowing is exercised rather
than
assumed. Flagging it because a hand-rolled stand-in for a real type is
exactly the
blind spot that hides a defect.

Security: no new surface. Selected transcript text already reached the
terminal
through this path on macOS and through `/copy` everywhere; the same
bytes now
travel on the other platforms, on a user-initiated drag, which is what a
native
terminal selection would have done anyway.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary

Bump the package version from 0.1.12 to 0.1.13 for the next release.
Only
pyproject.toml and uv.lock change.

Core changes since v0.1.12:

- Conversations now keep their selected model, with the follow-up
lifecycle
  and recovery paths aligned to that ownership.
- The TUI handles fast typing, prompt layout, and cross-platform
copy-on-select
  behavior more reliably.
- ask_user now keeps its timeout, validation, and delivery contracts
aligned
  across runtime paths.
- End-to-end trajectory debugging and regression workflows are
available.
- Configuration and Skill Hub failures now surface clearer, bounded
errors,
  and EverOS is updated from 1.2.1 to 1.2.3.

## Type

- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other

## Verification

- `uv lock` - resolved 193 packages and updated raven v0.1.12 to v0.1.13
- `uv lock --check` - resolved 193 packages successfully
- `uv export --locked --all-extras --no-hashes --no-emit-project -o
/tmp/raven-0.1.13-constraints.txt` - resolved 193 packages and wrote 624
lines
- `uv run pytest tests/test_package_skeleton.py::test_package_imports
tests/test_cli_smoke.py::test_version_flag_matches_installed_metadata
-x` - 2 passed
- `uv run raven --version` - reported Raven v0.1.13
- `git diff github/main...HEAD --check` - passed with no output

- [x] Relevant tests pass locally
- [ ] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

No lint, type, documentation, or screenshot changes are needed for this
version-only bump.

## Risk

- [ ] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

This changes package metadata only. Rollback by reverting the version
bump.

## Related Issues

N/A

Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
…ities visible (EverMind-AI#312)

## Summary

A WeChat user asked for a recommendation and the bot answered "the web
search
tool is not configured here". That sentence was not the model's own
phrasing --
it was the tool's error text, relayed outward. `web_search` needs a
Serper key
and that deployment has never had one, but the tool was registered
unconditionally, so on every search-shaped question the model saw it,
reached
for it, and passed the setup error on to whoever was in the chat.

Withholding the tool fixes that and creates a second problem: an
unregistered
tool is invisible. Nothing in a running Raven then says the capability
exists at
all -- the model is never offered it, no document lists it, and `raven
doctor`
reports on providers and memory but has never mentioned tools. The only
way to
learn that web search is one account and one edit away was to read the
source.

This does both halves, because either alone leaves a real gap: the
deployer is
told what this install can and cannot do, and the model is still not
offered a
tool it cannot run.

### Withholding the unusable tool

`web_search` is gated on a resolved key, in the main loop and in the
sub-agent
surface (`subagent/manager.py`) -- a sub-agent that reaches for a search
it
cannot run reports the failure to its caller, and that text lands in the
parent
turn, the same leak one level down.

The gate asks the tool, not the config. `WebSearchTool.api_key` resolves
at call
time from the constructor value *or* `SERPER_API_KEY`, so reading
`tools.web.search.apiKey` alone would withdraw a working tool from any
deploy
that exports the variable and configures nothing.

The tool's error message also hard-coded `~/.raven/config.json` while
the
gateway runs with `--config` elsewhere, so following it meant editing a
file the
process never reads. It names the path actually in force now. That text
is
reachable only if the key disappears after registration, which is
exactly why it
should be right: it is the message for the case the gate cannot cover.

### Three rules, and nowhere to read them

Registration is decided per family, each a different shape:

| family | rule |
|---|---|
| `web_search` | a resolved key, asked of the built tool |
| `web_fetch` | nothing -- always registered, a key only improves
extraction |
| media x3 | an `api_key` *or* a `model`, either counting as configured
|

For media those are two questions, not one. A section naming only a
model is
registered, because a model alone counts as asking for the tool, and
then every
call returns a missing-key error. Whether a capability is *offered* and
whether
it *works* come apart there, and a report treating them as one fact
ticks a
capability that cannot run.

Each rule is defensible where it sits. What is missing is anywhere to
read them.
Providers had the same sprawl once and answered it with
`providers.auth`: a
declarative table plus `credential_status` as the single authority, with
an AST
invariant enforcing that authority, because six surfaces had answered
the same
question six ways and each looked reasonable alone. Tools never got the
equivalent.

### What this adds

**Each tool answers for itself,** twice where the questions differ.
`WebSearchTool.is_configured` reads the config value *or*
`SERPER_API_KEY`,
because those are two sources and only the tool consults both. The media
base
answers `is_configured` on a model *or* a key -- which is what stops an
OpenRouter credential set for chat from silently switching on three
tools that
bill per call -- and answers `has_key` separately on the chain it
actually
resolves at call time: its own section, the borrowed provider key, then
`OPENROUTER_API_KEY`. Both rules live with the credential they read.

**`capabilities.py` describes the five** for a human deciding what to
set up:
what each does in one line, how much work it is (nothing / reuse a
credential
you already have / obtain an account), where the key goes, where to get
one, and
what it costs. It rules on nothing -- `is_configured` and
`has_credential` both
ask the tools -- so it cannot become a second opinion.

**`raven doctor` grows a section**, listing every capability configured
or not,
ordered by how much the deployer has to do:

```
(markers render as a green check and a yellow bang; spelled [ok] and [!] here to
keep this description ASCII, since it becomes the squash commit body)

Tool capabilities
  web_fetch:       [ok] Read a web page the agent already has the URL for
  image_generate:  [ok] Generate an image  (borrowed: providers.openrouter.apiKey)
  text_to_speech:  [!] Generate speech from text
                   no key resolves; calls will fail
                   set: tools.media.speech.apiKey
                   or env: OPENROUTER_API_KEY
  video_generate:  -  Generate a video
                   switch on: tools.media.video.model
                   key: reusing providers.openrouter.apiKey
                   Billed per call; needs prepaid OpenRouter credit.
  web_search:      -  Search the web
                   set: tools.web.search.apiKey
                   or env: SERPER_API_KEY
                   key from: https://serper.dev
  2 capability(s) available but not set up; the agent is not offered them.
```

Three deliberate details.

Naming the credential is load-bearing in both directions. A row that
cannot
distinguish a reused credential from a missing one sends someone to
create an
account they already have -- and a row that claims a reuse with nothing
to reuse
is worse, because acting on it means setting a model, getting a
registered tool,
and watching every call fail on a credential they were told they had. So
the
reuse line prints only when a key is genuinely there to pick up, and a
capability already in that broken state says so outright instead of
showing a
satisfied tick.

The credential is named at the path that holds it. For the media family
`config_path` names the *model*, so reusing it as the key source pointed
the
deployer at a line with no credential in it.

And each fact is on its own line rather than in a sentence, because the
terminal
wraps a long line mid-path and a config key broken across two rows
cannot be
copied, which is the only thing that row is for.

Nothing here moves the exit code, including the warned row. An install
without
image generation is a choice, not a fault, and a doctor that fails on it
teaches
people to ignore doctor. The half-finished one is arguable -- the memory
section
does exit non-zero for a role the user configured that the server could
not
build -- but that failure is silent where it happens, recall just
returning
nothing, whereas this one returns an error string to the model on every
call.
Say the word and it becomes an exit code instead.

### Not in this change

Registration still lives in `AgentLoop`. Having it read the table is the
point
of this shape and removes the last duplicate reader, but it edits
`agent/loop/main.py`, which is under active change, and it is worth
doing on its
own once that settles. Until then the table is a description, and the
tests
below are what keep it honest.

`deep_research` is deliberately absent: it is moving to the sub-agent
surface
and its tool is going away.

## Type

- [ ] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

Mixed on purpose: the first commit is a fix and the rest are the feature
it
made necessary. Splitting them would land a change that hides a
capability
without landing the one that makes it discoverable.

## Verification

```
make lint                                            exit 0
make build                                           exit 0
npm run test --prefix ui-tui                         969 passed, 13 skipped
uv run --all-extras pytest -q                        6186 passed, 35 skipped, 1 failed
uv run pytest tests/test_tool_capabilities.py \
              tests/test_cli_doctor_commands.py \
              tests/test_agent_loop_web_tools.py     64 passed
uv run --extra dev ruff check raven tests scripts    All checks passed
uv run --extra dev ruff format --check               828 files already formatted
npx commitlint --from origin/main --to HEAD          exit 0
scripts/check_commit_messages.py origin/main..HEAD   exit 0
```

The one failure is
`test_read_file_image.py::test_an_attachment_that_cannot_be_
read_costs_a_note_not_the_turn`, and it is not from this branch: it
fails the
same way on `main` at `1cb604a` with these commits absent. The case
makes a file
unreadable with `chmod 000`, which does not block a root user, so it
fails for
anyone running the suite as root and passes in CI. This branch does not
touch
that file or the code under it.

The capability tests drive a real `AgentLoop` and compare what it
registered
against what the table predicts, rather than asserting the table against
itself.
The gated set is derived -- tools present once credentials are supplied,
absent
without -- so a sixth gated tool whose author forgets the table fails
here rather
than going unnoticed.

That last claim was false when first written, and the last commit is
what makes
it true. The fixture listing the media tools by hand never switched a
fourth one
on, so a new one never joined the gated set and the assertion held over
an
already-incomplete table. EverMind-AI#305, which adds a MiniMax voice-clone tool,
is that
case: merged against this branch the assertion passed. Read from
`MediaGenConfig` instead, it fails and names the tool -- `gated but
undeclared:
['voice_clone']`.

Which means whichever of the two lands second turns this red,
deliberately. The
fix is one table entry, and it needs the rule that PR settles:
`voice_clone`
counts as configured on `api_key or api_base or model` in
`effective_media_config` while registration still gates on `api_key or
model`,
so an `apiBase`-only install with no MiniMax key is configured by one
rule and
withheld by the other. That is the divergence this table exists to make
visible,
and it is worth resolving there rather than papering over here.

Fifteen mutations, each caught:

```
the web_search gate removed from the main loop    3 failed
the same gate removed from the sub-agent surface  1 failed
media rule reads only the key                     7 failed
web_search rule reads only the config             1 failed
a tool removed from the table                     6 failed
the doctor section not rendered                   6 failed
doctor lists only configured tools                4 failed
unconfigured treated as a failure                17 failed
the reuse line is printed unconditionally         1 failed
the key source falls back to the model path       3 failed
a keyless registered capability is not flagged    1 failed
has_key always answers yes                        1 failed
has_credential collapses into is_configured       1 failed
the credential chain drops OPENROUTER_API_KEY     3 failed
the reuse check ignores the environment           3 failed
```

Four of those tests exist only because a mutation pass found the earlier
versions insufficient, and the last rounds are why the shape changed. A
media
case that sets both a model and an OpenRouter key proves nothing about
the "or
model" half: the borrow fills the key in, so a rule reading only the key
still
answers correctly. The case that pins it has nothing to borrow.

The sub-agent case is the same argument applied to the second call site:
with
the gate present only in the main loop, every test above still passes,
because
nothing was watching what the sub-agent surface registers.

More usefully, mutating `has_key` to always answer yes changed no test
at all in
the first version, which said the ruling was not load-bearing: the
doctor was
inferring "no credential" from an *empty source string* rather than from
the
tool's answer, so a fourth credential source would have been reported as
a
missing one. `has_credential` is now its own fact, asked of the tools,
and that
mutation fails.

Separately, autouse fixtures clear `SERPER_API_KEY` and
`OPENROUTER_API_KEY`,
without which several of these pass for the wrong reason on any machine
where a
developer exported one.

Rendering was checked against a real config in six states -- nothing
set, a
provider key present, a model with nothing to borrow, a tool with its
own key, a
model plus a borrowable key, and an exported variable as the only source
--
rather than only asserted on.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

One behaviour change: `web_search` is no longer offered to the model
when no key
resolves. That is the fix. A deployment that has a key, in config or in
the
environment, is unaffected; one that has none was getting an error
string in
place of an answer.

The rest is additive. No other registration logic changed, so which
tools an
agent is offered is otherwise exactly what it was; `is_configured` moved
the
existing predicates onto the tools without altering them, and the
mutation
results above are what pins that. `has_key` and `_resolve_key` are new
names for
the chain `api_key` already resolved -- the property calls the extracted
one, so
callers see the same answers.

`raven doctor` gains a section and no new exit code. Its zero-network
guarantee
holds: the table reads config and environment only.

Two allowlist entries were added to
`test_only_the_auth_module_decides_configuredness_from_a_key`, argued in
place:
`subagent/manager.py`, which asks the built tool whether a key resolved
so an
unusable search is withheld, and `capabilities.py`, which reads keys to
*report*
-- which source supplied one, and whether one is there to reuse. Both
ruling
halves are delegated to the tools, whose files were already listed.

Rollback is a revert. Nothing is written and no configuration is read
differently.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

---------

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary

Prevent a failed MCP transport from cancelling the main agent turn
during startup.

Keep each MCP transport and handshake in one lifecycle so SDK task-group
failures unwind into ordinary per-server connection errors. Raven can
then continue with the remaining MCP servers and enter the model loop.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

- `uv run pytest tests/test_sandbox_unit.py -x -q` - 70 passed.
- `uv run ruff check raven/agent/tools/mcp.py
tests/test_sandbox_unit.py` - passed.
- A real `raven agent` turn returned `RAVEN_FIXED_OK` after two
configured MCP servers failed authentication.
- Docs and screenshots are not needed because no user-facing contract
changed.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

The change only alters failure isolation during MCP initialization. Roll
back the single fix commit if transport lifecycle behavior regresses.

## Related Issues

N/A

---------

Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
…verMind-AI#366)

## Summary

PR EverMind-AI#360 reports every copy-on-select write as a permanent transcript
line, so a
reading session that drags through the transcript piles up "copied N
characters" rows under the conversation. The confirmation splits by what
the
line is for: the unbounded terse repeats become a transient notice
rendered
above the input box -- one line that replaces itself on every new copy
and
clears after 3 seconds -- while the first copy of a session keeps its
path
caveat in the transcript. The caveat is the answer a user comes looking
for
after a paste comes up empty minutes later, and it cannot pile up
because it
is once per session by construction.

The reporter still owns the per-session caveat tally and now returns
`{ text, firstOfSession }` so the caller can route the caveat to the
transcript and the repeats to the notice. A new `copyNoticeStore` holds
the
single notice; `showCopyNotice` keeps one dismiss timer, clearing any
pending
one before arming the next, so an earlier deadline can never clear a
newer
notice. `/copy` is deliberately untouched: an explicit command's answer
still
belongs in the transcript, and it cannot stack on its own.

## Type

- [x] Fix

## Verification

Run from `ui-tui/` at branch head, in a fresh worktree of `github/main`.
Dependencies came from `npm ci` in `ui-tui/` and in the hermes package;
the
hermes package's own `node_modules` holds only esbuild, so the ink
render
tests run against one react copy (two copies break every hook call).

```
npx vitest run --no-file-parallelism
  Test Files  93 passed (93)
  Tests  1084 passed | 13 skipped (1097)

npm run type-check
  clean, exit 0

npm run lint
  22 problems (0 errors, 22 warnings)

npx prettier --check src/app/copyNoticeStore.ts src/__tests__/copyNotice.test.tsx
  src/__tests__/clipboard.test.ts src/app/useMainApp.ts
  src/components/appLayout.tsx src/lib/clipboard.ts
  All matched files use Prettier code style!

python3 -m scripts.check_commit_messages github/main..HEAD
  exit 0
```

The 22 lint warnings are the baseline, not new: 22 before the branch and
22
after, none in a file this branch touches. The suite is run with
`--no-file-parallelism` deliberately: the ink render tests fail under
default
worker parallelism at this suite size, and no CI job runs them.

Suites that exercise the changed code directly:
`src/__tests__/copyNotice.test.tsx`
(7 cases), `src/__tests__/clipboard.test.ts` (30 cases, the 3 reporter
cases
updated to the new return shape).

New tests were proved load-bearing rather than assumed:

- Removing the notice render line in `appLayout.tsx` makes the render
test
  fail (content and placement above the input box).
- Removing the pending-timer clear in `showCopyNotice` makes the
re-shown-notice test fail: the earlier 3s deadline then clears the newer
  notice.
- The dismissed-notice case now shows a notice first, and removing
  `dismissCopyNotice`'s atom clear turns it red (review fix).

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

No doc change. `ui-tui/CONTEXT.md` has no clipboard or selection entry,
and
"copy notice" continues the existing "notice" vocabulary of
`copyResultNotice`
/ `copyOnSelectNotice`, so nothing new is coined.

## Risk

Behaviour change: the per-drag copy confirmation is now transient and
above
the input box instead of a permanent transcript line, and the first copy
of a
session lands one transcript line, exactly as on `main`. Reverting the
branch
restores the EverMind-AI#360 behaviour; no data or on-disk format is involved.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

---------

Co-authored-by: Claude (deepseek-v4-pro) <noreply@anthropic.com>
## Summary

- Position Raven as both the runnable open-source harness and the
broader research and ecosystem direction.
- Define Raven as The Harness of Harnesses and explain the All-Domain
Collaboration Network.
- Highlight Trusted, Persistent, and Evolving behavior plus the
evaluation, execution, verification, memory, and feedback loop in
English and Chinese.
- Surface the 22-task evaluation and quality-cost Pareto-frontier
positioning before the published benchmark table.
- Replace the old banner with a 129 KB GitHub-hosted Raven visual.

## Type

- [ ] Fix
- [ ] Feature
- [x] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `.venv/bin/pre-commit run --files README.md README.zh-CN.md` - passed
- `env UV_CACHE_DIR=/private/tmp/raven-uv-cache make check-large-files`
- passed
- `git diff --check origin/main...HEAD` - passed
- `git merge-tree --write-tree HEAD origin/main` - clean
- No runtime tests were run because this change only updates
documentation and externally hosted artwork.

- [ ] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

The change affects repository messaging only. Rollback is a revert of
the documentation commit and restoration of the previous banner URL.

## Related Issues

N/A
@guix4ever
guix4ever force-pushed the fix/mcp_cancelled_error_escape branch from f56d604 to 889e859 Compare August 27, 2026 03:01
cyfyifanchen and others added 2 commits August 27, 2026 16:42
…d-AI#368)

## Summary

- Clarify that the current public Raven release is the runnable
self-improving Agent Harness.
- Present The Harness of Harnesses as Raven's next-version direction and
internal research prototype, not a capability of the current public
release.
- Align the English and Chinese banner notes, section headings,
future-tense descriptions, and benchmark labels around that release
boundary.

## Type

- [ ] Fix
- [ ] Feature
- [x] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- [ ] Relevant tests pass locally - not run because this is a docs-only
change.
- [x] Relevant lint / type checks pass locally - `.venv/bin/pre-commit
run --files README.md README.zh-CN.md` passed.
- [x] User-facing docs or screenshots are updated when needed
- `UV_CACHE_DIR=/private/tmp/raven-uv-cache make check-large-files`
passed.
- `git diff --check origin/main...HEAD` passed.

## Risk

- [x] Security impact considered - no runtime code or dependencies
changed.
- [x] Backward compatibility considered - documentation only.
- [x] Rollback path is clear for risky changes - revert the
documentation commit.

## Related Issues

N/A
## Summary

- Replace the previous project link lists with the shared EverMind
ecosystem tables from EverOS.
- Keep the English and Chinese README sections aligned above their
contributing sections.

## Type

- [ ] Fix
- [ ] Feature
- [x] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

No runtime commands were run because this is a documentation-only
change. Both README files were checked for one ecosystem section, exact
shared table content, and valid placement.

- [ ] Relevant tests pass locally
- [ ] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A
@guix4ever
guix4ever force-pushed the fix/mcp_cancelled_error_escape branch from 889e859 to 991d583 Compare August 28, 2026 02:58
forrestjgq and others added 2 commits August 28, 2026 16:20
…erMind-AI#370)

## Summary

`raven trajectory` had eight id-oriented subcommands; using them meant
copying ids out of `list` by hand. This PR changes the attempt data
model
and adds the human face, in four phases:

1. **Attempt definitions** (`attempts.json` sidecar): an attempt id
equals
the trace id unless a definition groups several traces under one minted
id. Merge creates a definition (absorbing prior definitions and legacy
   groups as aliases, so verdicts/pins recorded under old ids stay
visible); split deletes it, migrating pins down to members - naturally
undoable, span logs stay append-only. Pin migration is linearized under
   a fixed lock order (attempts > pins); every failure and concurrency
   interleaving over-protects, never under-protects. Legacy logs
(span-level `attempt.id`) stay addressable and mergeable, but cannot be
   split (their grouping lives in append-only spans).
2. **Write-side removal**: the span-level attempt mechanism (the
   begin/end/current_attempt trio and the `attempt.id` span attribute,
   shipped in v0.1.13) is deleted; `attempt.id` is now a reserved
   attribute key stripped from caller input, so `attempts.json` is the
sole grouping source by mechanism. Readers keep resolving legacy logs;
   zero data migration. This is a breaking API change, declared in the
   BREAKING CHANGE footer below.
3. **CLI adaptation and reader defect tolerance**: `list` folds
definition
   members into one row and surfaces verdicts through the alias set; new
`merge`/`split` subcommands wrap the data layer thinly; `unpin` clears
   definition, aliases, members, and the literal id in one transaction.
   Two whole crash surfaces are closed along the way: Rich markup
   injection (a legal id may contain `[/red]`; every dynamic value is
   escaped), and JSON-legal but type-broken records (per-line UTF-8
   decoding, required-field string validation, non-object attribute
   containers) now degrade per record/field instead of killing commands.
4. **Interactive browser**: a bare `raven trajectory` on a TTY opens a
three-screen questionary flow (sessions -> attempts -> actions: save /
   report / minimize / verdict / pin|unpin / split, plus multi-select
   merge). Menus and action messages never show a session key, trace id,
or attempt id - only artifact paths carry ids. Every action triggers a
full rescan (report bundles and auto-pins before confirming, so even an
   aborted action changed state); data-layer errors surface as one fixed
   id-free message; aggregation reads one snapshot per refresh and
deduplicates records into logical spans keyed by (traceId, spanId), so
   a root turn's checkpoint+final pair counts once.

CONTEXT.md gains the Attempt Definition term and the final-state Attempt
wording.

## Type

- [ ] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `uv run pytest tests/test_cli_trajectory_browse.py
tests/test_cli_trajectory_commands.py tests/test_trajectory_store.py
tests/test_trajectory_bundle.py tests/test_tracing_api.py -q` -> 241
passed
- `uv run pytest tests/ -q --ignore=tests/integration` -> 6838 passed;
the 11 failures + 20 errors match main's pre-existing
environment-specific set node-id for node-id (cron timezone cases,
everos/config root-permission cases, viewer probe, theme), compared as
sorted FAILED/ERROR sets against a baseline recorded before this branch
- `uv run ruff check .` and `uv run ruff format --check .` -> clean

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed
(CONTEXT.md terms)

## Risk

- Bare `raven trajectory` changes behavior: it opens the browser on a
TTY
(previously the help page) and exits 2 with a hint when non-interactive.
  Scripts are unaffected: the subcommand check runs before the TTY gate.
- New spans no longer carry `attempt.id`, and `trace.begin_attempt` /
`trace.end_attempt` / `trace.current_attempt` are removed. These shipped
  in v0.1.13, so an integration calling them raises AttributeError right
after upgrading - an explicit, immediately visible failure rather than a
  silent one. Migration: group attempts after recording with
  `merge_attempts()` (library) or `raven trajectory merge` (CLI); reader
  fallback keeps existing logs addressable with zero data migration.
- `attempts.json` is the only new mutable state; deleting it reverts
every
  attempt to single-turn semantics. Span logs remain append-only.
- Rollback: revert the squash commit; no data migration either way. The
one
known crash window (process death between merge's two file writes)
leaves
  a harmless over-protective pin, documented in the store module.

- [x] Security impact considered (markup-injection and path-escape
surfaces
  closed; ids treated as untrusted in every renderer)
- [x] Backward compatibility considered (legacy span-attribute logs stay
  addressable, mergeable, and listable)
- [x] Rollback path is clear for risky changes

## Related Issues

EverMind-AI#362 (reference only: this PR removes the write-side attempt mechanism
introduced there and shipped in v0.1.13). No issue is closed.

BREAKING CHANGE: trace.begin_attempt, trace.end_attempt, and
trace.current_attempt (shipped in v0.1.13) and the span-level attempt.id
attribute are removed. Group attempts after recording instead:
merge_attempts() in raven.trajectory, or `raven trajectory merge` in the
CLI. Existing logs carrying a span-level attempt.id stay addressable
through the reader fallback; no data migration is needed.

---------

Co-authored-by: 江国庆 <guoqingjiang@deepglint.com>
Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…-AI#371)

## Summary

Unit tests were leaking tracing spans into the real `~/.raven/traces`
store. Tracing is on by default and `trace.span()` calls are embedded in
library code (agent loop, subagents, TUI RPC), so any test exercising
those paths emitted spans with fabricated session keys (`session-a`,
`weixin:c`, `tui:default`, ...). Those spans then surfaced as phantom
sessions in `raven trajectory`, while the TUI `/sessions` list (which
reads the real session store) stayed clean.

This PR adds an autouse fixture in `tests/conftest.py` that:

- redirects `RAVEN_TRACING_DIR` to a per-test tmp directory, and
- resets the lazy module-level `TraceStore` cache
(`raven.tracing.spans._store`) around each test, since it pins the
directory resolved at first emit.

Key decisions:

- Only the directory is redirected, not the enabled switch, so the suite
keeps exercising the real emission path with zero behavior change; leaks
land in tmp instead.
- This centralizes the setenv-plus-cache-reset pattern that 8
tracing/trajectory test files already perform manually. Those tests keep
working: their own `monkeypatch.setenv` runs after the fixture and wins,
and they already reset the cache themselves.
- Existing phantom sessions come from archived span logs written before
this fix; the fixture only prevents new leaks. One archive file mixes a
real session with test keys, so cleanup needs line-level filtering and
is left out of scope.

## Type

- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other

Test-suite hygiene (`test` commit type).

## Verification

- `uv run pytest tests/test_tracing_api.py
tests/test_trajectory_store.py tests/test_trajectory_bundle.py
tests/test_trajectory_replay.py tests/test_trajectory_redact.py
tests/test_cli_tracing_commands.py tests/test_cli_trajectory_commands.py
tests/test_cli_trajectory_browse.py -q`: 308 passed, 1 failed
(`test_viewer_health_true_for_our_viewer`, fails identically on
unmodified `main`, environment-specific)
- `uv run pytest tests/test_agent_loop_run_emit.py
tests/test_subagent_manager.py tests/test_tui_rpc_turn_send.py
tests/test_deep_research_tool.py tests/test_agent_loop_approval.py -q`:
109 passed; the real `~/.raven/traces` log did not grow during the run
- Positive check: running `tests/test_agent_loop_run_emit.py` with
`--basetemp` shows the previously leaked session keys (`cli:c`, `tg:c`,
`weixin:c`) now land in per-test tmp dirs
- `uv run pytest tests/ --ignore=tests/integration -q`: 6837 passed, 11
failed, 20 errors; the same failure set reproduces on unmodified `main`
(environment-specific: cron/tz, everos server, theme, config loader,
viewer health), zero regressions from this change
- `uv run ruff check tests/conftest.py` and `uv run ruff format --check
tests/conftest.py`: both pass

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

Docs box left blank: test-only change, no user-facing docs affected.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

Test-only change; no production code touched. One known interaction:
`RAVEN_TRACING_DIR` takes precedence over `RAVEN_HOME`, so a future test
that fakes `RAVEN_HOME` and expects traces under it must also set
`RAVEN_TRACING_DIR` itself. Rollback: revert the single commit.

## Related Issues

N/A

Co-authored-by: 江国庆 <guoqingjiang@deepglint.com>
Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
@guix4ever
guix4ever force-pushed the fix/mcp_cancelled_error_escape branch from 991d583 to 6f84d44 Compare August 30, 2026 16:48
LivXue and others added 2 commits August 31, 2026 10:45
…ify headroom (EverMind-AI#372)

## Summary

Onboard model-default refreshes plus an EverOS startup fix:

1. Rerank default: Qwen/Qwen3-Reranker-4B has been retired from
OpenRouter, so the onboard wizard's rerank example and EN/ZH
recommendations now point to qwen/qwen3-reranker-8b, which OpenRouter
still serves.

2. Stale defaults: the memory-LLM capability floor (gpt-4.1-mini), the
multimodal recommendation (google/gemini-3-flash-preview), and the Azure
OpenAI provider default (gpt-5.2-chat) all point at generations behind
what OpenRouter/Azure serve today. They now read qwen/qwen3.8-flash (the
EverOS client always sends temperature, which the GPT-5.x reasoning
family rejects), google/gemini-3.7-flash, and gpt-5.6-sol. GPT-5.x Azure
deployments force reasoning_effort="none" on tool calls, matching
Microsoft's tool-calling requirement; configured effort still applies to
non-tool calls.

3. Startup failure: an exhausted fs.inotify.max_user_instances makes the
spawned EverOS server's watcher die at boot with "OSError: [Errno 24]
inotify instance limit reached", which surfaces only as a cryptic exit
buried in the dead-child log. ensure_everos_server now measures per-user
inotify headroom (counting same-uid inotify instances via /proc/<pid>/fd
links that read anon_inode:inotify against the kernel cap) before
spawning: it raises the cap when the process is privileged, otherwise it
fails fast with the exact sudo sysctl commands. A dead child whose log
blames inotify gets the same hint appended to its error.

## Type

- [x] Fix

## Verification

- make coverage (full default suite, uv run --frozen --python 3.12
--all-extras pytest -q): 7119 passed, 2 failed, 37 skipped, 13
deselected. The 2 failures are pre-existing on main and
environment-dependent (reproduced on main in a throwaway worktree): both
expect a chmod-based write to be refused, which cannot happen when the
suite runs as root (CAP_DAC_OVERRIDE):
-
tests/test_config_loader.py::test_migration_is_correct_even_when_the_file_cannot_be_written
-
tests/test_everos_server.py::TestAnUnwritableRootFailsAsAStartFailure::test_it_surfaces_as_runtime_error
- make coverage-diff COVERAGE_BASE_REF=origin/main: 100.00% (57/57
executable changed lines), passed the 90.00% threshold
- make coverage-ratchet: passed (line +2.11pp, branch +3.24pp over
baseline)
- uv run pytest tests/test_cli_onboard_commands.py: 251 passed
- uv run pytest tests/test_azure_openai_provider.py: 9 passed
- uv run ruff check (changed files): clean
- uv run ruff format --check (changed files): clean
- pre-commit commitlint hook (conventional commit message): passed on
all commits

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally

## Risk

- Security impact considered: the sysctl write runs only when the
process already holds the privilege to write /proc/sys; otherwise the
gate only returns the instructions.
- Backward compatibility considered: the gate is a no-op when headroom
exists or when procfs/sysctl cannot be read (non-Linux); the
model-default changes are wizard text and one provider default constant,
no config migration needed.
- Rollback path is clear: revert the merge; the check is isolated to
ensure_everos_server and three module-level helpers.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A
…d-AI#375)

## Summary

The Feishu adapter's reconnect loop retried on a fixed 5s sleep with no
backoff and no attempt cap. lark-oapi's ws Client.start() blocks while
the connection lives (the SDK reconnects transient drops internally with
its own 120s interval) and only returns by raising, almost always a
ClientException such as bad credentials or connection-limit exceeded.
The old loop therefore hammered Feishu's auth endpoint every 5s on
permanent failures.

Changes:

- Retries now use exponential backoff capped at 300s (5s initial, factor
  2): 5s, 10s, 20s, 40s, 80s, 160s, 300s.
- ClientException is logged as a permanent error instead of a generic
  warning.
- The backoff sleep runs in 1s slices so stop() can interrupt a long
  backoff promptly (no lingering thread).

Three tests added covering ladder growth/cap, ClientException handling,
and stop() interrupting the sleep.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

Commands and results:

- `uv run pytest tests/test_channels_feishu.py -q` -> 38 passed
  (1 warning is pre-existing, from test_stop_blocks_zombie_inbound).
- `uv run ruff check raven/channels/adapters/feishu/channel.py
  tests/test_channels_feishu.py` -> All checks passed.
- `uv run ruff format raven/channels/adapters/feishu/channel.py
  tests/test_channels_feishu.py` -> 2 files reformatted (applied).
- pre-commit hooks all passed, including the conventional commit message
  check.

No user-facing docs affected: this is an internal retry-cadence change
with no config or interface change.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

No security surface change: only retry timing is affected. No config,
CLI, or API change; existing deployments behave the same apart from
slower retries after repeated connection failures. Rollback: revert the
single commit.

## Related Issues

N/A

---------

Co-authored-by: Claude (deepseek-v4-pro) <noreply@anthropic.com>
@guix4ever
guix4ever force-pushed the fix/mcp_cancelled_error_escape branch from 6f84d44 to 309eca9 Compare September 1, 2026 02:53
forrestjgq and others added 2 commits September 5, 2026 23:58
## Summary

Overhaul the interactive `raven trajectory` browser end to end, fixing
every
issue from real-terminal testing:

- Navigation: every screen opens with a help area under its title (page
  purpose plus "[Key] function" hotkey hints, rendered in a new readable
help color). Esc walks one level up (action -> attempt -> session); on
the
top-level session screen Esc stays put and quitting is Ctrl+C only,
which
prints a single exit notice. Esc inside a running action cancels just
that
action through a dedicated exception at the action boundary, so the back
  sentinel can never reach a conversion or the data layer; a cancelled
  report keeps its pre-confirm bundle-and-pin side effects (the existing
  declined-report contract). Back/Exit menu entries are gone; answered
  prompts erase themselves and navigation is echoed as breadcrumb lines
  with markup-escaped untrusted text.
- Tables: the session list is TITLE | ATTEMPTS | LAST ACTIVITY and the
  attempt list is # | STARTED | TURNS | SPANS | VERDICT | PIN | MERGED |
PREVIEW with green check cells. Time cells keep the year. The layout is
  budgeted per screen build from the live terminal width (row indent, a
safety margin, and inter-column gaps are all part of the math); PREVIEW
  drops as a whole column when it no longer fits, and below the dynamic
  minimum width the tightest layout is kept while the renderer clips
  overlong lines at the terminal edge. Every dynamic cell passes a
  single-line sanitization gate before width math (verdict sidecars only
  guarantee a non-empty string), cells are measured and truncated by
terminal display width (CJK is 2 cells), and fixed column widths always
  fit their headers.
- Space preview: Space on an attempt row prints that attempt's per-turn
input/output previews, collected in the same snapshot scan and sorted by
a fully-stringified key so ordering never follows log source order (the
  table PREVIEW cell derives from the same sorted collection). Any key,
arrow keys included, returns with the cursor kept on the row; the waiter
  passes the terminal's CPR reply through to the renderer instead of
  treating it as a keypress, and Ctrl+C keeps the browser-wide cancel.
- Merge hotkey: with two or more attempts, m/M opens the multi-select
merge
  (advertised in the help line; the old "Merge attempts" menu entry is
gone). The checkbox screen carries its own help line for toggle,
confirm,
  and cancel. A single attempt neither binds nor advertises the key.
- Theme and feel: new success (green check) and help (readable hint
text)
colors in both palettes, all WCAG AA on their backgrounds; a lone ESC is
flushed after 50ms instead of prompt_toolkit's 0.5s default, removing
the
  laggy Esc feel.

The data layer is untouched: changes are confined to
raven/cli/trajectory_browse.py, raven/cli/_theme.py, and their tests.
The
id-taking subcommands (raven trajectory save/list/...) behave as before.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `COLORTERM=truecolor uv run pytest tests/test_cli_theme.py
tests/test_cli_trajectory_browse.py
tests/test_cli_trajectory_commands.py -q` -> 220 passed
- `COLORTERM=truecolor uv run pytest tests/ --ignore=tests/integration
-q` -> 6917 passed; the 10 failed + 20 errors are the pre-existing
environment-dependent set, identical file-by-file on a clean main
checkout (cron, everos server, config loader, tracing viewer)
- `uv run ruff check` and `uv run ruff format --check` on the four
touched files -> clean
- PTY smoke on a CPR-answering pseudo terminal at 100 and 58 columns:
full flow (preview, merge cancel, action screen, staged Esc backout,
Ctrl+C quit) and the narrow-width degradation contract
- New coverage includes real pipe-input key-binding tests (Esc on all
four prompt kinds, Space/m/M hits, waiter key paths with CPR
pass-through), table budget/clipping boundaries at the computed minimum
width, and corrupt verdict/timestamp corpora

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- The interactive browser's look and key protocol change wholesale (that
is
  the point of this PR): Esc no longer quits from the top level (Ctrl+C
  does), Back/Exit entries are gone, and lists render as tables. The
machine-facing subcommands and the trajectory data layer are untouched.
- Rollback: revert the squash commit; the browser returns to the
previous
  interaction wholesale, no data migration involved.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

---------

Co-authored-by: 江国庆 <guoqingjiang@deepglint.com>
Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
@gloryfromca

Copy link
Copy Markdown
Member

This PR was auto-closed by a history rewrite, not by a maintainer

On 2026-09-12 the main branch of this repository was replaced with a linearized history: all merge commits were folded into a single line and author emails were unified. Every commit received a new SHA, so your branch and main no longer share a common ancestor and GitHub can no longer compute a diff. That is why this PR was closed automatically; the API refuses to reopen it.

Your work is safe in your fork. Nothing in your repository was modified.

To restore this PR, rebase onto the new main and open a fresh one. The pre-rewrite main is preserved as a tag, which makes the rebase exact:

git remote add upstream https://github.com/EverMind-AI/Raven.git   # if you don't have it
git fetch upstream 'refs/tags/backup/pre-linearize-20260912:refs/tags/old-main'
git fetch upstream main
git rebase --onto upstream/main $(git merge-base <your-branch> old-main) <your-branch>
git push --force-with-lease

Then open a new PR and reference this one. Thank you for the contribution, and sorry for the disruption.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: prevent a bare CancelledError during MCP connection from terminating the gateway